This commit is contained in:
Albert S. 2018-11-02 21:48:36 +01:00
parent 21b208bff9
commit 352989756c
1 changed files with 161 additions and 174 deletions

View File

@ -9,25 +9,20 @@ use std::ffi::CStr;
extern crate libc; extern crate libc;
use libc::passwd; use libc::passwd;
struct Entry struct Entry {
{ users: Vec<String>,
users : Vec<String>, dest_user: String,
dest_user : String, cmd: String,
cmd : String, args: String,
args : String, argv0: String,
argv0 : String, inherit_envs: Vec<String>,
inherit_envs : Vec<String>, no_new_privs: bool,
no_new_privs : bool, arbitrary_args: bool,
arbitrary_args : bool,
} }
impl Entry impl Entry {
{ fn new() -> Entry {
fn new() -> Entry return Entry {
{
return Entry{
users: Vec::new(), users: Vec::new(),
dest_user: String::new(), dest_user: String::new(),
cmd: String::new(), cmd: String::new(),
@ -36,162 +31,138 @@ impl Entry
inherit_envs: Vec::new(), inherit_envs: Vec::new(),
no_new_privs: true, no_new_privs: true,
arbitrary_args: false, arbitrary_args: false,
} };
} }
} }
struct Passwd struct Passwd {
{ pw_name: String,
pw_name : String, pw_passwd: String,
pw_passwd : String, pw_uid: libc::uid_t,
pw_uid : libc::uid_t, pw_gid: libc::gid_t,
pw_gid : libc::gid_t, pw_gecos: String,
pw_gecos : String, pw_dir: String,
pw_dir : String, pw_shell: String,
pw_shell : String,
} }
fn initgroups(user : &str, group : libc::gid_t) -> std::io::Result<()> fn initgroups(user: &str, group: libc::gid_t) -> std::io::Result<()> {
{
let userarg = CString::new(user); let userarg = CString::new(user);
return errnowrapper( unsafe { libc::initgroups(userarg.unwrap().as_ptr(), group)}); return errnowrapper(unsafe {
libc::initgroups(userarg.unwrap().as_ptr(), group)
});
} }
fn errnowrapper(ret : libc::c_int) -> std::io::Result<()> fn errnowrapper(ret: libc::c_int) -> std::io::Result<()> {
{ if ret != 0 {
if ret != 0 return Err(Error::last_os_error());
{ }
return Err(Error::last_os_error());
}
return Ok(()); return Ok(());
} }
fn setuid(id : libc::uid_t) -> std::io::Result<()> fn setuid(id: libc::uid_t) -> std::io::Result<()> {
{ return errnowrapper(unsafe { libc::setuid(id) });
return errnowrapper(unsafe { libc::setuid(id)});
} }
fn setgid(gid : libc::gid_t) -> std::io::Result<()> fn setgid(gid: libc::gid_t) -> std::io::Result<()> {
{ return errnowrapper(unsafe { libc::setgid(gid) });
return errnowrapper(unsafe { libc::setgid(gid)});
} }
fn geteuid() -> u32 fn geteuid() -> u32 {
{ unsafe {
unsafe return libc::geteuid();
{
return libc::geteuid();
}
}
fn getpwnam(username : &str) -> std::io::Result<Passwd>
{
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()); fn getpwnam(username: &str) -> std::io::Result<Passwd> {
if pwnamresult.is_null() fn getstr(str: *mut libc::c_char) -> String {
{ unsafe { CStr::from_ptr(str).to_string_lossy().into_owned() }
return Err(Error::last_os_error()); }
} unsafe {
Ok( Passwd { let pwnamresult: *mut passwd = libc::getpwnam(CString::new(username).unwrap().as_ptr());
pw_name: getstr((*pwnamresult).pw_name), if pwnamresult.is_null() {
pw_passwd: getstr((*pwnamresult).pw_passwd), return Err(Error::last_os_error());
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),
} )
} }
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<()> fn ensure_allowed(userid: libc::uid_t, entry: &Entry) -> std::io::Result<()> {
{
if userid == 0 { if userid == 0 {
return Ok(()); return Ok(());
} }
for user in &entry.users for user in &entry.users {
{ let passwd: Passwd = getpwnam(&user)?;
let passwd : Passwd = getpwnam(&user)?; if passwd.pw_uid == userid {
if passwd.pw_uid == userid return Ok(());
{
return Ok(());
} }
} }
let passwd : Passwd = getpwnam(&entry.dest_user)?; let passwd: Passwd = getpwnam(&entry.dest_user)?;
if passwd.pw_uid == userid if passwd.pw_uid == userid {
{
return Ok(()); return Ok(());
} }
return Err(Error::new(ErrorKind::PermissionDenied, "Not allowed to become target user")); return Err(Error::new(
ErrorKind::PermissionDenied,
"Not allowed to become target user",
));
} }
fn usage() fn usage() {
{
println!("Usage: raou ENRTYNAME"); println!("Usage: raou ENRTYNAME");
} }
fn add_multi(vec : &mut Vec<String>, val: String) fn add_multi(vec: &mut Vec<String>, val: String) {
{ if val.contains(',') {
if val.contains(',')
{
let splitted = val.split(','); let splitted = val.split(',');
for part in splitted { for part in splitted {
vec.push(part.to_owned()); vec.push(part.to_owned());
} }
} } else {
else
{
vec.push(val); vec.push(val);
} }
} }
fn assign(entry : &mut Entry, key: &str, value: &str) fn assign(entry: &mut Entry, key: &str, value: &str) {
{
let val = value.to_owned(); let val = value.to_owned();
match key match key {
{ "path" => entry.cmd = val,
"path" => entry.cmd = val, "user" => add_multi(&mut entry.users, val),
"user" => add_multi(&mut entry.users, val), "env_vars" => add_multi(&mut entry.inherit_envs, val),
"env_vars" => add_multi(&mut entry.inherit_envs, val), "argv0" => entry.argv0 = val,
"argv0" => entry.argv0 = val, "target_user" => entry.dest_user = val,
"target_user" => entry.dest_user = val, "allow_args" => entry.arbitrary_args = (val == "1" || val == "true"),
"allow_args" => entry.arbitrary_args = ( val == "1" || val == "true") , "args" => entry.args = val,
"args" => entry.args = val, "no_new_privs" => entry.no_new_privs = (val == "1" || val == "true"),
"no_new_privs" => entry.no_new_privs = (val == "1" || val == "true"), _ => {
_ => { eprintln!("Ignoring invalid key {}", key);
eprintln!("Ignoring invalid key {}", key);
}
} }
}
} }
fn assign_from_line(entry : &mut Entry, line : &str) fn assign_from_line(entry: &mut Entry, line: &str) {
{
let mut splitted = line.splitn(2, ' '); let mut splitted = line.splitn(2, ' ');
let key = splitted.next(); let key = splitted.next();
let value = splitted.next(); let value = splitted.next();
if ! key.is_some() || ! value.is_some() if !key.is_some() || !value.is_some() {
{
return; return;
} }
assign(entry, key.unwrap(), value.unwrap()) assign(entry, key.unwrap(), value.unwrap())
} }
fn create_entry_from_file(filepath : &str) -> std::io::Result<Entry> fn create_entry_from_file(filepath: &str) -> std::io::Result<Entry> {
{ let mut entry: Entry = Entry::new();
let mut entry : Entry = Entry::new();
let f = File::open(filepath)?; let f = File::open(filepath)?;
let bf = BufReader::new(f); let bf = BufReader::new(f);
@ -203,44 +174,40 @@ fn create_entry_from_file(filepath : &str) -> std::io::Result<Entry>
} }
//TODO: clearenv does not set errno? //TODO: clearenv does not set errno?
fn clearenv() -> std::io::Result<()> fn clearenv() -> std::io::Result<()> {
{
return errnowrapper(unsafe { libc::clearenv() }); return errnowrapper(unsafe { libc::clearenv() });
} }
//TODO: AsRef for envs? //TODO: AsRef for envs?
fn setup_environment(passwd : &Passwd, envs : &[String]) -> std::io::Result<()> fn setup_environment(passwd: &Passwd, envs: &[String]) -> std::io::Result<()> {
{ let saved_envs: Vec<String> = envs.iter()
let saved_envs : Vec<String> = envs.iter().map(|s| std::env::var(s).expect("No such var")).collect(); .map(|s| std::env::var(s).expect("No such var"))
.collect();
clearenv()?; clearenv()?;
//TODO: set_var does not have a return val? //TODO: set_var does not have a return val?
std::env::set_var("HOME", &passwd.pw_dir); std::env::set_var("HOME", &passwd.pw_dir);
std::env::set_var("USER", &passwd.pw_name); std::env::set_var("USER", &passwd.pw_name);
std::env::set_var("LOGNAME", &passwd.pw_name); std::env::set_var("LOGNAME", &passwd.pw_name);
std::env::set_var("SHELL", &passwd.pw_shell); std::env::set_var("SHELL", &passwd.pw_shell);
for (i, item) in saved_envs.iter().enumerate() for (i, item) in saved_envs.iter().enumerate() {
{
std::env::set_var(&envs[i], item); std::env::set_var(&envs[i], item);
} }
Ok(()) Ok(())
} }
fn become_user(passwd : &Passwd) -> std::io::Result<()> fn become_user(passwd: &Passwd) -> std::io::Result<()> {
{
initgroups(&(passwd.pw_name), passwd.pw_gid)?; initgroups(&(passwd.pw_name), passwd.pw_gid)?;
setgid(passwd.pw_gid)?; setgid(passwd.pw_gid)?;
setuid(passwd.pw_uid)?; setuid(passwd.pw_uid)?;
std::env::set_current_dir(&passwd.pw_dir)?; std::env::set_current_dir(&passwd.pw_dir)?;
Ok(()) Ok(())
} }
fn init_sandbox(entry : &Entry) -> std::io::Result<()> fn init_sandbox(entry: &Entry) -> std::io::Result<()> {
{ if (entry.no_new_privs) {
if(entry.no_new_privs)
{
errnowrapper(unsafe { errnowrapper(unsafe {
libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0,0,0) libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)
})?; })?;
} }
@ -248,46 +215,46 @@ fn init_sandbox(entry : &Entry) -> std::io::Result<()>
} }
#[inline(always)] #[inline(always)]
fn to_cstring<T: AsRef<str>>(s : T) -> * const libc::c_char { fn to_cstring<T: AsRef<str>>(s: T) -> *const libc::c_char {
return CString::new(s.as_ref()).unwrap().into_raw(); return CString::new(s.as_ref()).unwrap().into_raw();
} }
fn create_execv_args(entry : & Entry, cmdargs : &Vec<String>) -> Vec<* const libc::c_char> fn create_execv_args(entry: &Entry, cmdargs: &Vec<String>) -> Vec<*const libc::c_char> {
{
let mut args : Vec<* const libc::c_char>; let mut args: Vec<*const libc::c_char>;
if entry.arbitrary_args && cmdargs.len() > 2 if entry.arbitrary_args && cmdargs.len() > 2 {
{
args = cmdargs.iter().skip(2).map(to_cstring).collect(); args = cmdargs.iter().skip(2).map(to_cstring).collect();
} else {
args = entry
.args
.as_str()
.split_whitespace()
.map(to_cstring)
.collect();
} }
else if !&entry.argv0.is_empty() {
{
args = entry.args.as_str().split_whitespace().map(to_cstring).collect();
}
if ! &entry.argv0.is_empty()
{
args.insert(0, to_cstring(&entry.argv0)); args.insert(0, to_cstring(&entry.argv0));
} } else {
else
{
let cmdbegin = &entry.cmd.rfind("/").unwrap() + 1; let cmdbegin = &entry.cmd.rfind("/").unwrap() + 1;
args.insert(0, to_cstring(&entry.cmd.split_at(cmdbegin).1)); args.insert(0, to_cstring(&entry.cmd.split_at(cmdbegin).1));
} }
args.push(std::ptr::null()); args.push(std::ptr::null());
return args; return args;
} }
fn exec(entryname : &str, cmdargs : &Vec<String>) -> std::io::Result<()> fn exec(entryname: &str, cmdargs: &Vec<String>) -> std::io::Result<()> {
{ let mut filepath: String = String::from("/etc/raou.d/");
let mut filepath : String = String::from("/etc/raou.d/");
filepath = filepath + entryname; filepath = filepath + entryname;
if ! std::path::Path::new(&filepath).exists() { if !std::path::Path::new(&filepath).exists() {
return Err(std::io::Error::new(ErrorKind::NotFound, "The entry ".to_owned() + &filepath + " does not exist")); return Err(std::io::Error::new(
ErrorKind::NotFound,
"The entry ".to_owned() + &filepath + " does not exist",
));
} }
let entry : Entry = create_entry_from_file(&filepath)?; let entry: Entry = create_entry_from_file(&filepath)?;
let destuserpasswd : Passwd = getpwnam(&entry.dest_user)?; let destuserpasswd: Passwd = getpwnam(&entry.dest_user)?;
let currentuser : u32 = geteuid(); let currentuser: u32 = geteuid();
let args = create_execv_args(&entry, &cmdargs); let args = create_execv_args(&entry, &cmdargs);
@ -295,34 +262,54 @@ fn exec(entryname : &str, cmdargs : &Vec<String>) -> std::io::Result<()>
ensure_allowed(currentuser, &entry)?; ensure_allowed(currentuser, &entry)?;
become_user(&destuserpasswd).or_else(|e| return Err(Error::new(ErrorKind::PermissionDenied, "Failed to switch user: ".to_owned() + &e.to_string())))?; become_user(&destuserpasswd).or_else(|e| {
setup_environment(&destuserpasswd, &entry.inherit_envs).or_else(|e| return Err(Error::new(ErrorKind::Other, "Environment setup failure: ".to_owned() + &e.to_string())))?; return Err(Error::new(
init_sandbox(&entry).or_else(|e| return Err(Error::new(ErrorKind::Other, "Sandbox init failure: ".to_owned() + &e.to_string())))?; ErrorKind::PermissionDenied,
"Failed to switch user: ".to_owned() + &e.to_string(),
));
})?;
setup_environment(&destuserpasswd, &entry.inherit_envs)
.or_else(|e| {
return Err(Error::new(
ErrorKind::Other,
"Environment setup failure: ".to_owned() + &e.to_string(),
));
})?;
init_sandbox(&entry).or_else(|e| {
return Err(Error::new(
ErrorKind::Other,
"Sandbox init failure: ".to_owned() + &e.to_string(),
));
})?;
unsafe { unsafe {
errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr())).or_else(|e| return Err(Error::new(ErrorKind::Other, "execv failed: ".to_owned() + &e.to_string())))?; errnowrapper(libc::execv(to_cstring(entry.cmd), args.as_ptr()))
.or_else(|e| {
return Err(Error::new(
ErrorKind::Other,
"execv failed: ".to_owned() + &e.to_string(),
));
})?;
} }
std::process::exit(0); std::process::exit(0);
Ok(()) Ok(())
} }
fn main() -> Result<(), std::io::Error> { fn main() -> Result<(), std::io::Error> {
let argv : Args = std::env::args(); let argv: Args = std::env::args();
let cmdargs : Vec<String> = argv.collect(); let cmdargs: Vec<String> = argv.collect();
let entryname = cmdargs.get(1); let entryname = cmdargs.get(1);
if entryname.is_some() { if entryname.is_some() {
match exec(&entryname.unwrap(), &cmdargs) { match exec(&entryname.unwrap(), &cmdargs) {
Err(e) => { Err(e) => {
eprintln!("The following error ocurred:"); eprintln!("The following error ocurred:");
eprintln!("{}", e); eprintln!("{}", e);
std::process::exit(1); std::process::exit(1);
} }
_ => { _ => {}
};
}
} ;
} }
usage(); usage();
std::process::exit(1); std::process::exit(1);