summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main.rs188
1 files changed, 188 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..dd8eeb3
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,188 @@
+use std::fs;
+use std::{env, os::unix::prelude::PermissionsExt};
+use std::process::ExitCode;
+use std::time::SystemTime;
+use pwd::Passwd;
+use nix::{unistd};
+use serde_json::Value;
+
+extern crate time;
+
+fn main() -> ExitCode {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ eprintln!("Invalid argument count.");
+ return ExitCode::from(0);
+ }
+ let config = match config("/etc/crab.conf") {
+ Some(data) => data,
+ None => return ExitCode::from(1)
+ };
+ let user = match Passwd::current_user() {
+ Some(data) => data,
+ None => {
+ eprintln!("You dont exist.");
+ return ExitCode::from(2);
+ }
+ };
+ let persist = match allowed(&config, &user.name) {
+ Some(data) => data,
+ None => {
+ eprintln!("Operation Not Permitted. This incidence will be reported.");
+ return ExitCode::from(3);
+ }
+ };
+
+ if !validate(&user.name, persist) {
+ eprintln!("Authentication failed.");
+ return ExitCode::from(4);
+ }
+
+ if !unistd::setuid(unistd::geteuid()).is_ok() || !unistd::setgid(unistd::getegid()).is_ok() {
+ eprintln!("Failed to set root permissions");
+ return ExitCode::from(5);
+ };
+
+ let err = exec::execvp(&args[1], &args[1..]);
+ println!("Error: {}", err);
+
+ ExitCode::from(0)
+}
+
+struct Config {
+ users: Vec<(String, bool)>
+}
+
+fn validate(user: &str, persist: bool) -> bool {
+ if persist && get_persist(user) {
+ return true;
+ }
+ let input = rpassword::prompt_password(format!("crab ({}) password: ", user)).unwrap();
+ let mut auth = pam::Authenticator::with_password("crab").unwrap();
+ auth.get_handler().set_credentials(user.to_owned(), input);
+ if !auth.authenticate().is_ok() || !auth.open_session().is_ok() {
+ return false;
+ }
+ if persist {
+ set_persist(user);
+ }
+ return true;
+}
+
+fn allowed(config: &Config, user: &str) -> Option<bool> {
+ for (name, persist) in &config.users {
+ if name == user {
+ return Some(persist.clone());
+ }
+ }
+ None
+}
+
+fn config(path: &str) -> Option<Config> {
+ let file = match std::fs::read_to_string(path) {
+ Err(e) => {
+ eprintln!("{}: {}", &path, e);
+ return None
+ },
+ Ok(data) => data
+ };
+
+ let mut users = vec![];
+ for (line_num, line) in file.split("\n").enumerate() {
+ let args: Vec<&str> = line.split(" ").collect();
+ if line.trim() == "" {
+ continue;
+ }
+ if args.len() < 2 {
+ eprintln!("Error in config at line {}: Not enough arguments", line_num);
+ continue;
+ }
+ let user: String = args[0].to_string();
+ let persist: bool = match args[1].parse() {
+ Err(e) => {
+ eprintln!("Error in config at line {}: {}", line_num, e);
+ continue;
+ },
+ Ok(data) => data
+ };
+ users.push((user, persist));
+ }
+ Some(Config{users})
+}
+
+fn get_terminal_process() -> Option<i32> {
+ let id: i32 = match std::process::id().try_into() {
+ Ok(data) => data,
+ Err(_) => return None
+ };
+ let stat = match procinfo::pid::stat(id) {
+ Ok(data) => data,
+ Err(_) => return None
+ };
+ Some(stat.tty_nr)
+}
+
+fn get_terminal_config() -> Option<Value> {
+ let id = match get_terminal_process() {
+ Some(data) => data,
+ None => return None
+ };
+ let data = match std::fs::read_to_string(path(&id)) {
+ Ok(data) => data,
+ Err(_) => "{}".to_string()
+ };
+ let json: Value = match serde_json::from_str(&data) {
+ Ok(data) => data,
+ Err(_) => return None
+ };
+ Some(json)
+}
+
+fn write_terminal_config(id: &i32, data: &str) -> Result<(), Box<dyn std::error::Error>> {
+ std::fs::write(path(&id), data)?;
+ unistd::chown(std::path::Path::new(&path(&id)), Some(unistd::Uid::from(0)), Some(unistd::Gid::from(0)))?;
+ let metadata = std::fs::metadata(path(&id))?;
+ let mut perms = metadata.permissions();
+ perms.set_mode(0o0660);
+ fs::set_permissions(path(&id), perms)?;
+ Ok(())
+}
+
+
+fn get_persist(user: &str) -> bool {
+ let json = match get_terminal_config() {
+ Some(data) => data,
+ None => return false
+ };
+ let timestamp = match json[user].as_u64() {
+ Some(data) => data,
+ None => return false
+ };
+ return now() - timestamp < 60 * 3;
+}
+
+fn set_persist(user: &str) {
+ let mut json = match get_terminal_config() {
+ Some(data) => data,
+ None => return
+ };
+ json[user] = Value::from(now());
+ let id = match get_terminal_process() {
+ Some(data) => data,
+ None => return
+ };
+ match write_terminal_config(&id, &json.to_string()) {
+ Ok(_) => {},
+ Err(e) => {
+ eprintln!("Internal Error: {}", e)
+ }
+ };
+}
+
+fn now() -> u64 {
+ return SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs();
+}
+
+fn path(id: &i32) -> String {
+ return format!("/tmp/crab-{}", id);
+} \ No newline at end of file