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.

216 lines
5.8 KiB

7 years ago
8 years ago
5 years ago
8 years ago
8 years ago
5 years ago
7 years ago
7 years ago
7 years ago
8 years ago
8 years ago
5 years ago
5 years ago
4 years ago
7 years ago
7 years ago
7 years ago
5 years ago
7 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
8 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
2 years ago
8 years ago
  1. use async_trait::async_trait;
  2. use std::error::Error;
  3. use std::ffi::OsStr;
  4. use std::io::Result as IoResult;
  5. use std::process::Output;
  6. use std::process::Stdio;
  7. use tokio::io::AsyncWriteExt;
  8. use tokio::process::Command;
  9. #[macro_export]
  10. macro_rules! args {
  11. ($($x:expr),*) => (
  12. &[$($x.as_ref()),*]
  13. );
  14. ($($x:expr,)*) => (args![$($x),*]) // handle trailing commas
  15. }
  16. fn check_success(output: Output) -> Result<Output, Box<dyn Error>> {
  17. if output.status.success() {
  18. Ok(output)
  19. } else {
  20. Err(String::from_utf8(output.stderr)?.into())
  21. }
  22. }
  23. pub fn is_success(res: Result<Output, impl Error + 'static>) -> Result<Output, Box<dyn Error>> {
  24. check_success(res?)
  25. }
  26. pub fn get_output(output: Output) -> Result<Vec<u8>, Box<dyn Error>> {
  27. Ok(check_success(output)?.stdout)
  28. }
  29. #[async_trait(?Send)]
  30. pub trait CommandRunner {
  31. async fn run(&self, program: &str, args: &[&OsStr], stdin: &str) -> IoResult<Output>;
  32. async fn run_with_args(&self, program: &str, args: &[&OsStr]) -> IoResult<Output> {
  33. self.run(program, args, "").await
  34. }
  35. async fn get_output(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
  36. let output = self.run_with_args(program, args).await?;
  37. get_output(output)
  38. }
  39. async fn run_successfully(&self, program: &str, args: &[&OsStr]) -> Result<(), Box<dyn Error>> {
  40. is_success(self.run(program, args, "").await)?;
  41. Ok(())
  42. }
  43. async fn get_stderr(&self, program: &str, args: &[&OsStr]) -> Result<Vec<u8>, Box<dyn Error>> {
  44. Ok(is_success(self.run_with_args(program, args).await)?.stderr)
  45. }
  46. }
  47. #[derive(Debug)]
  48. pub struct StdCommandRunner;
  49. #[async_trait(?Send)]
  50. impl CommandRunner for StdCommandRunner {
  51. async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
  52. //println!("{} {:?}", program, args);
  53. let mut child = Command::new(program)
  54. .args(args)
  55. .stdin(Stdio::piped())
  56. .stdout(Stdio::piped())
  57. .stderr(Stdio::piped())
  58. .spawn()
  59. .expect("Failed to spawn child process");
  60. let stdin = child.stdin.as_mut().expect("Failed to open stdin");
  61. stdin
  62. .write_all(input.as_bytes())
  63. .await
  64. .expect("Failed to write to stdin");
  65. let res = child.wait_with_output().await;
  66. //println!("{:?}", res);
  67. #[allow(clippy::let_and_return)]
  68. res
  69. }
  70. }
  71. #[derive(Debug)]
  72. pub struct SetuidCommandRunner<'a, U: AsRef<str>, C: CommandRunner> {
  73. command_runner: &'a C,
  74. user_name: U,
  75. }
  76. impl<'a, U: AsRef<str>, C: CommandRunner> SetuidCommandRunner<'a, U, C> {
  77. pub fn new(user_name: U, command_runner: &'a C) -> Self {
  78. SetuidCommandRunner {
  79. command_runner,
  80. user_name,
  81. }
  82. }
  83. }
  84. use std::env;
  85. use users::get_user_by_name;
  86. struct TempSetEnv<'a> {
  87. name: &'a str,
  88. old_value: Option<String>,
  89. }
  90. impl<'a> TempSetEnv<'a> {
  91. fn new(name: &'a str, new_value: String) -> TempSetEnv<'a> {
  92. let old_value = env::var(name);
  93. env::set_var(name, new_value);
  94. TempSetEnv {
  95. name,
  96. old_value: old_value.ok(),
  97. }
  98. }
  99. }
  100. impl Drop for TempSetEnv<'_> {
  101. fn drop(&mut self) {
  102. match self.old_value {
  103. Some(ref val) => env::set_var(self.name, val),
  104. None => env::remove_var(self.name),
  105. }
  106. }
  107. }
  108. #[async_trait(?Send)]
  109. impl<U: AsRef<str>, C: CommandRunner> CommandRunner for SetuidCommandRunner<'_, U, C> {
  110. async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
  111. let uid = get_user_by_name(self.user_name.as_ref())
  112. .expect("User does not exist")
  113. .uid();
  114. let set_home = TempSetEnv::new("HOME", format!("/home/{}", self.user_name.as_ref()));
  115. let set_dbus = TempSetEnv::new("XDG_RUNTIME_DIR", format!("/run/user/{}", uid));
  116. //println!("{} {:?}", program, args);
  117. let mut child = Command::new(program)
  118. .args(args)
  119. .stdin(Stdio::piped())
  120. .uid(uid)
  121. .gid(uid)
  122. .stdout(Stdio::piped())
  123. .stderr(Stdio::piped())
  124. .spawn()
  125. .expect("Failed to spawn child process");
  126. let stdin = child.stdin.as_mut().expect("Failed to open stdin");
  127. stdin
  128. .write_all(input.as_bytes())
  129. .await
  130. .expect("Failed to write to stdin");
  131. let res = child.wait_with_output().await;
  132. drop(set_home);
  133. drop(set_dbus);
  134. //println!("{:?}", res);
  135. res
  136. }
  137. }
  138. #[derive(Debug)]
  139. pub struct SuCommandRunner<'a, C>
  140. where
  141. C: CommandRunner,
  142. {
  143. command_runner: &'a C,
  144. user_name: &'a str,
  145. }
  146. impl<'a, C> SuCommandRunner<'a, C>
  147. where
  148. C: 'a + CommandRunner,
  149. {
  150. pub fn new(user_name: &'a str, command_runner: &'a C) -> SuCommandRunner<'a, C> {
  151. SuCommandRunner {
  152. command_runner,
  153. user_name,
  154. }
  155. }
  156. }
  157. // Su doesn't set XDG_RUNTIME_DIR
  158. // https://github.com/systemd/systemd/blob/master/src/login/pam_systemd.c#L439
  159. #[async_trait(?Send)]
  160. impl<'a, C> CommandRunner for SuCommandRunner<'a, C>
  161. where
  162. C: 'a + CommandRunner,
  163. {
  164. async fn run(&self, program: &str, args: &[&OsStr], input: &str) -> IoResult<Output> {
  165. let raw_new_args = [self.user_name, "-s", "/usr/bin/env", "--", program];
  166. let mut new_args: Vec<&OsStr> = raw_new_args.iter().map(AsRef::as_ref).collect();
  167. new_args.extend_from_slice(args);
  168. self.command_runner.run("su", &new_args, input).await
  169. }
  170. }
  171. #[cfg(test)]
  172. mod test {
  173. use crate::args;
  174. use crate::async_utils::run;
  175. use crate::command_runner::{CommandRunner, StdCommandRunner};
  176. use futures_util::future::FutureExt;
  177. use std::time::Instant;
  178. #[test]
  179. fn test() {
  180. let c = StdCommandRunner;
  181. run(async {
  182. let args = args!["1"];
  183. let start = Instant::now();
  184. let res = c.run("sleep", args, "").fuse();
  185. let ps = c.run("ps", args![], "").fuse();
  186. futures_util::pin_mut!(res, ps);
  187. loop {
  188. futures_util::select! {
  189. _ = res => {},
  190. _ = ps => assert!((Instant::now() - start).as_millis() < 1000),
  191. complete => break,
  192. }
  193. }
  194. })
  195. }
  196. }