commit ce0742d33599d005e44c8e6e3687bf24287964b3 Author: Albert S Date: Thu Oct 4 20:45:29 2018 +0200 initial commit diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9d9169f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,14 @@ +[[package]] +name = "libc" +version = "0.2.43" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "raou" +version = "0.1.0" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..acac1f5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "raou" +version = "0.1.0" +authors = ["Albert S. "] + +[dependencies] +libc = "0.2.43" \ No newline at end of file diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4ed5d80 --- /dev/null +++ b/install.sh @@ -0,0 +1,2 @@ +#!/bin/sh +cp target/release/raou /usr/bin/raou ; chmod o=rx /usr/bin/raou ; setcap 'cap_setuid=ep cap_setgid=ep' /usr/bin/raou diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cae6d48 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,324 @@ +use std::env::Args; +use std::io::{Error, ErrorKind}; +use std::fs::File; +use std::io::BufReader; +use std::io::BufRead; +use std::ffi::CString; +use std::ffi::CStr; + +extern crate libc; +use libc::passwd; + +struct Entry +{ + users : Vec, + dest_user : String, + cmd : String, + args : String, + argv0 : String, + inherit_envs : Vec, + no_new_privs : bool, + arbitrary_args : bool, + + +} + +impl Entry +{ + fn new() -> Entry + { + return Entry{ + users: Vec::new(), + dest_user: String::new(), + cmd: String::new(), + args: String::new(), + argv0: String::new(), + inherit_envs: Vec::new(), + no_new_privs: true, + arbitrary_args: false, + } + } +} + + +struct Passwd +{ + pw_name : String, + pw_passwd : String, + pw_uid : libc::uid_t, + pw_gid : libc::gid_t, + pw_gecos : String, + pw_dir : String, + pw_shell : String, +} + +fn initgroups(user : &str, group : libc::gid_t) -> std::io::Result<()> +{ + let userarg = CString::new(user); + return errnowrapper( unsafe { libc::initgroups(userarg.unwrap().as_ptr(), group)}); +} + + +fn errnowrapper(ret : libc::c_int) -> std::io::Result<()> +{ + if ret != 0 + { + return Err(Error::last_os_error()); + } + return Ok(()); +} + +fn setuid(id : libc::uid_t) -> std::io::Result<()> +{ + return errnowrapper(unsafe { libc::setuid(id)}); +} + +fn setgid(gid : libc::gid_t) -> std::io::Result<()> +{ + return errnowrapper(unsafe { libc::setgid(gid)}); +} + +fn geteuid() -> u32 +{ + unsafe + { + return libc::geteuid(); + } +} + +fn getpwnam(username : &str) -> std::io::Result +{ + fn getstr(str : *mut libc::c_char) -> String + { + unsafe + { + CStr::from_ptr(str).to_string_lossy().into_owned() + } + } + unsafe + { + let pwnamresult : *mut passwd = libc::getpwnam(CString::new(username).unwrap().as_ptr()); + if pwnamresult.is_null() + { + return Err(Error::last_os_error()); + } + Ok( Passwd { + pw_name: getstr((*pwnamresult).pw_name), + pw_passwd: getstr((*pwnamresult).pw_passwd), + pw_uid: (*pwnamresult).pw_uid, + pw_gid: (*pwnamresult).pw_gid, + pw_gecos: getstr((*pwnamresult).pw_gecos), + pw_dir: getstr((*pwnamresult).pw_dir), + pw_shell: getstr((*pwnamresult).pw_shell), + } ) + } + +} + +fn ensure_allowed(userid : libc::uid_t, entry : &Entry ) -> std::io::Result<()> +{ + if userid == 0 { + return Ok(()); + } + for user in &entry.users + { + let passwd : Passwd = getpwnam(&user)?; + if passwd.pw_uid == userid + { + return Ok(()); + } + } + + let passwd : Passwd = getpwnam(&entry.dest_user)?; + if passwd.pw_uid == userid + { + return Ok(()); + } + return Err(Error::new(ErrorKind::PermissionDenied, "Not allowed to become target user")); +} + + +fn usage() +{ + println!("Usage: raou ENRTYNAME"); +} +fn add_multi(vec : &mut Vec, val: String) +{ + if val.contains(',') + { + let splitted = val.split(','); + for part in splitted { + vec.push(part.to_owned()); + } + } + else + { + vec.push(val); + } +} +fn assign(entry : &mut Entry, key: &str, value: &str) +{ + let val = value.to_owned(); + match key + { + "path" => entry.cmd = val, + "user" => add_multi(&mut entry.users, val), + "env_vars" => add_multi(&mut entry.inherit_envs, val), + "argv0" => entry.argv0 = val, + "target_user" => entry.dest_user = val, + "allow_args" => entry.arbitrary_args = ( val == "1" || val == "true") , + "args" => entry.args = val, + "no_new_privs" => entry.no_new_privs = (val == "1" || val == "true"), + _ => { + eprintln!("Ignoring invalid key {}", key); + } + + } +} +fn assign_from_line(entry : &mut Entry, line : &str) +{ + let mut splitted = line.splitn(2, ' '); + let key = splitted.next(); + let value = splitted.next(); + + if ! key.is_some() || ! value.is_some() + { + return; + } + assign(entry, key.unwrap(), value.unwrap()) + +} +fn create_entry_from_file(filepath : &str) -> std::io::Result +{ + let mut entry : Entry = Entry::new(); + let f = File::open(filepath)?; + + let bf = BufReader::new(f); + + for line in bf.lines() { + assign_from_line(&mut entry, &line.unwrap()); + } + Ok(entry) +} + +//TODO: clearenv does not set errno? +fn clearenv() -> std::io::Result<()> +{ + return errnowrapper(unsafe { libc::clearenv() }); +} +//TODO: AsRef for envs? +fn setup_environment(passwd : &Passwd, envs : &[String]) -> std::io::Result<()> +{ + let saved_envs : Vec = envs.iter().map(|s| std::env::var(s).expect("No such var")).collect(); + clearenv()?; + + //TODO: set_var does not have a return val? + std::env::set_var("HOME", &passwd.pw_dir); + std::env::set_var("USER", &passwd.pw_name); + std::env::set_var("LOGNAME", &passwd.pw_name); + std::env::set_var("SHELL", &passwd.pw_shell); + + for (i, item) in saved_envs.iter().enumerate() + { + std::env::set_var(&envs[i], item); + } + Ok(()) + +} + +fn become_user(passwd : &Passwd) -> std::io::Result<()> +{ + initgroups(&(passwd.pw_name), passwd.pw_gid)?; + setgid(passwd.pw_gid)?; + setuid(passwd.pw_uid)?; + std::env::set_current_dir(&passwd.pw_dir)?; + Ok(()) +} +fn init_sandbox(entry : &Entry) -> std::io::Result<()> +{ + if(entry.no_new_privs) + { + errnowrapper(unsafe { + libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0,0,0) + })?; + } + + Ok(()) +} + +#[inline(always)] +fn to_cstring>(s : T) -> * const libc::c_char { + return CString::new(s.as_ref()).unwrap().into_raw(); +} + +fn create_execv_args(entry : & Entry, cmdargs : &Vec) -> Vec<* const libc::c_char> +{ + + + let mut args : Vec<* const libc::c_char>; + if entry.arbitrary_args && cmdargs.len() > 2 + { + args = cmdargs.iter().skip(2).map(to_cstring).collect(); + } + else + { + args = (&entry.args).split_whitespace().map(to_cstring).collect(); + } + if ! &entry.argv0.is_empty() + { + args.insert(0, to_cstring(&entry.argv0)); + } + else + { + args.insert(0, to_cstring(&entry.cmd)); + } + args.push(std::ptr::null()); + return args; +} +fn exec(entryname : &str, cmdargs : &Vec) -> std::io::Result<()> +{ + let mut filepath : String = String::from("/etc/raou.d/"); + filepath = filepath + entryname; + + let entry : Entry = create_entry_from_file(&filepath)?; + let destuserpasswd : Passwd = getpwnam(&entry.dest_user)?; + let currentuser : u32 = geteuid(); + + + let args = create_execv_args(&entry, &cmdargs); + + + + ensure_allowed(currentuser, &entry)?; + become_user(&destuserpasswd)?; + setup_environment(&destuserpasswd, &entry.inherit_envs)?; + init_sandbox(&entry)?; + + unsafe + { + errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr()))?; + } + std::process::exit(0); + Ok(()) +} +fn main() -> Result<(), std::io::Error> { + + let argv : Args = std::env::args(); + let cmdargs : Vec = argv.collect(); + let entryname = cmdargs.get(1); + if entryname.is_some() { + match exec(&entryname.unwrap(), &cmdargs) { + Err(e) => { + eprintln!("The following error ocurred:"); + eprintln!("{}", e); + + std::process::exit(1); + } + _ => { + + } + } ; + } + usage(); + std::process::exit(1); +}