use std::error::Error; use std::ffi::OsStr; use std::io::{Result as IoResult, Write}; use std::process::Command; use std::process::Output; use std::process::Stdio; pub trait CommandRunner { fn run_with_args_and_stdin + ?Sized>( &self, program: &str, args: &[&S], stdin: &str, ) -> IoResult; fn run_with_args + ?Sized>( &self, program: &str, args: &[&S], ) -> IoResult { self.run_with_args_and_stdin(program, args, "") } fn get_output + ?Sized>( &self, program: &str, args: &[&S], ) -> Result, Box> { let output = try!(self.run_with_args(program, args)); if !output.status.success() { return Err(try!(String::from_utf8(output.stderr)).into()); } Ok(output.stdout) } fn get_stderr + ?Sized>( &self, program: &str, args: &[&S], ) -> Result, Box> { let output = try!(self.run_with_args(program, args)); if !output.status.success() { return Err(try!(String::from_utf8(output.stderr)).into()); } Ok(output.stderr) } fn run_successfully + ?Sized>( &self, program: &str, args: &[&S], ) -> Result<(), Box> { let output = try!(self.run_with_args(program, args)); if output.status.success() { Ok(()) } else { Err(try!(String::from_utf8(output.stderr)).into()) } } } #[derive(Debug)] pub struct StdCommandRunner; impl CommandRunner for StdCommandRunner { fn run_with_args_and_stdin + ?Sized>( &self, program: &str, args: &[&S], input: &str, ) -> IoResult { // FIXME: logger //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("Failed to spawn child process"); let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all(input.as_bytes()) .expect("Failed to write to stdin"); let res = child.wait_with_output(); println!("{:?}", res); res } } #[derive(Debug)] pub struct SetuidCommandRunner<'a, C> where C: 'a + CommandRunner, { command_runner: &'a C, user_name: &'a str, } impl<'a, C> SetuidCommandRunner<'a, C> where C: 'a + CommandRunner, { pub fn new(user_name: &'a str, command_runner: &'a C) -> SetuidCommandRunner<'a, C> { SetuidCommandRunner { command_runner, user_name, } } } use std::env; use std::os::unix::process::CommandExt; use users::get_user_by_name; struct TempSetEnv<'a> { name: &'a str, old_value: Option, } impl<'a> TempSetEnv<'a> { fn new(name: &'a str, new_value: String) -> TempSetEnv<'a> { let old_value = env::var(name); env::set_var(name, new_value); TempSetEnv { name, old_value: old_value.ok(), } } } impl<'a> Drop for TempSetEnv<'a> { fn drop(&mut self) { match self.old_value { Some(ref val) => env::set_var(self.name, val), None => env::remove_var(self.name), } } } impl<'a, C> CommandRunner for SetuidCommandRunner<'a, C> where C: 'a + CommandRunner, { fn run_with_args_and_stdin + ?Sized>( &self, program: &str, args: &[&S], input: &str, ) -> IoResult { let uid = get_user_by_name(self.user_name) .expect("User does not exist") .uid(); let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name)); let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{}", uid)); //println!("{} {:?}", program, args); let mut child = Command::new(program) .args(args) .stdin(Stdio::piped()) .uid(uid) .gid(uid) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("Failed to spawn child process"); let stdin = child.stdin.as_mut().expect("Failed to open stdin"); stdin .write_all(input.as_bytes()) .expect("Failed to write to stdin"); let res = child.wait_with_output(); println!("{:?}", res); res } } #[derive(Debug)] pub struct SuCommandRunner<'a, C> where C: 'a + CommandRunner, { command_runner: &'a C, user_name: &'a str, } impl<'a, C> SuCommandRunner<'a, C> where C: 'a + CommandRunner, { pub fn new(user_name: &'a str, command_runner: &'a C) -> SuCommandRunner<'a, C> { SuCommandRunner { command_runner, user_name, } } } // Su doesn't set XDG_RUNTIME_DIR // https://github.com/systemd/systemd/blob/master/src/login/pam_systemd.c#L439 impl<'a, C> CommandRunner for SuCommandRunner<'a, C> where C: 'a + CommandRunner, { fn run_with_args_and_stdin + ?Sized>( &self, program: &str, args: &[&S], input: &str, ) -> IoResult { let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program]; let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(|s| s.as_ref()).collect(); let old_args: Vec<&OsStr> = args.iter().map(|s| s.as_ref()).collect(); new_args.extend_from_slice(&old_args); self .command_runner .run_with_args_and_stdin("su", &new_args, input) } } #[cfg(test)] mod test {}