A library for writing host-specific, single-binary configuration management and deployment tools
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

193 lines
4.9 KiB

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;
#[macro_export]
macro_rules! args {
($($x:expr),*) => (
&[$($x.as_ref()),*]
);
($($x:expr,)*) => (args![$($x),*]) // handle trailing commas
}
pub trait CommandRunner {
fn run_with_args_and_stdin(
&self,
program: &str,
args: &[&OsStr],
stdin: &str,
) -> IoResult<Output>;
fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult<Output> {
self.run_with_args_and_stdin(program, args, "")
}
fn get_output(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
let output = self.run_with_args(program, args)?;
if !output.status.success() {
return Err(String::from_utf8(output.stderr)?.into());
}
Ok(output.stdout)
}
fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
let output = self.run_with_args(program, args)?;
if !output.status.success() {
return Err(String::from_utf8(output.stderr)?.into());
}
Ok(output.stderr)
}
fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box<dyn Error>> {
self.get_output(program, args).map(|_| ())
}
}
#[derive(Debug)]
pub struct StdCommandRunner;
impl CommandRunner for StdCommandRunner {
fn run_with_args_and_stdin(
&self,
program: &str,
args: &[&OsStr],
input: &str,
) -> IoResult<Output> {
// 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, U: AsRef<str>, C: CommandRunner> {
command_runner: &'a C,
user_name: U,
}
impl<'a, U: AsRef<str>, C: CommandRunner> SetuidCommandRunner<'a, U, C> {
pub fn new(user_name: U, command_runner: &'a C) -> Self {
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<String>,
}
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, U: AsRef<str>, C: CommandRunner> CommandRunner for SetuidCommandRunner<'a, U, C> {
fn run_with_args_and_stdin(
&self,
program: &str,
args: &[&OsStr],
input: &str,
) -> IoResult<Output> {
let uid = get_user_by_name(self.user_name.as_ref())
.expect("User does not exist")
.uid();
let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref()));
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(
&self,
program: &str,
args: &[&OsStr],
input: &str,
) -> IoResult<Output> {
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();
new_args.extend_from_slice(args);
self
.command_runner
.run_with_args_and_stdin("su", &new_args, input)
}
}
#[cfg(test)]
mod test {}