1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
use std::io::{BufRead, BufReader, Write};
use std::process::{Command, Stdio};
/// Represents a shell with root privileges.
pub struct RootShell {
shell_process: std::process::Child,
}
/// String that is appended to the end of each command, to indicate that the command has finished.
const END_OF_COMMAND: &str = "~end-of-command~";
/// Implementation of RootShell
impl RootShell {
/// Creates a new root shell.
/// Defaults to pkexec as super user provider and sh as shell.
/// Returns None if the root shell could not be created.
/// # Example
/// ```
/// use super_shell::RootShell;
/// let mut root_shell = RootShell::new().expect("Failed to crate root shell");
/// println!("{}", root_shell.execute("echo Hello $USER"));
/// ```
#[cfg(target_os = "linux")]
pub fn new() -> Option<Self> {
Self::spawn_root_shell("pkexec", "sh")
}
#[cfg(target_os = "windows")]
pub fn new() -> Option<Self> {
None
}
#[cfg(target_os = "windows")]
pub fn new_custom(super_user_provider: &str, shell: &str) -> Option<Self> {
None
}
/// Creates a new root shell with the specified super user provider and shell.
/// Returns None if the root shell could not be created.
/// sudo as an interactive super user provider is currently not supported.
/// # Parameter
/// * `super_user_provider` - The command to use to get super user privileges
/// * `shell` - The shell to use
/// # Example
/// ```
/// use super_shell::RootShell;
/// let mut root_shell = RootShell::new_custom("gksu", "bash").expect("Failed to crate root shell");
/// println!("{}", root_shell.execute("echo Hello $USER"));
/// ```
#[cfg(target_os = "linux")]
pub fn new_custom(super_user_provider: &str, shell: &str) -> Option<Self> {
Self::spawn_root_shell(super_user_provider, shell)
}
/// Creates a new root shell with the specified super user provider and shell.
/// Returns None if the root shell could not be created.
/// # Parameter
/// * `super_user_provider` - The command to use to get super user privileges
/// * `shell` - The shell to use
fn spawn_root_shell(super_user_provider: &str, shell: &str) -> Option<RootShell> {
let shell_process = Command::new(super_user_provider)
.arg(shell)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.unwrap();
let mut root_shell = Self { shell_process };
// Make sure we are root now
let user = root_shell.execute("whoami");
if !user.trim().eq("root") {
return None;
}
Some(root_shell)
}
/// Executes a command in the root shell and returns the output trimmed.
/// Blocks the current thread until the command is finished.
/// # Parameter
/// * `command` - The command to execute
/// # Example
/// ```
/// use super_shell::RootShell;
/// let mut root_shell = RootShell::new().expect("Failed to crate root shell");
/// assert!(root_shell.execute("echo Hello $USER").trim().eq("Hello root"));
/// ```
pub fn execute(&mut self, command: impl AsRef<str>) -> String {
// If not linux return empty string
#[cfg(not(target_os = "linux"))]
return "".to_string();
// Append end of command string to the command
let command = command.as_ref().to_string();
// Write the actual command to stdin of the root shell
let mut shell_stdin = self.shell_process.stdin.as_mut().unwrap();
writeln!(&mut shell_stdin, "{}", command).unwrap();
shell_stdin.flush().unwrap();
// Write "end of command" string to stdin of the root shell
writeln!(&mut shell_stdin, "echo {}", END_OF_COMMAND).unwrap();
shell_stdin.flush().unwrap();
// Read piped stdout from the root shell
let stdout = self.shell_process.stdout.as_mut().unwrap();
let mut stdout_reader = BufReader::new(stdout);
// Read until the "end of command" string is found
let mut string_data = String::new();
loop {
let mut line = String::new();
stdout_reader.read_line(&mut line).unwrap();
string_data.push_str(&line);
if line.contains(END_OF_COMMAND) {
break;
}
}
// Clean up the string
let cmd_response = string_data.replace(END_OF_COMMAND, "");
let cmd_response = cmd_response.trim().to_string();
cmd_response
}
/// Exits the root shell and waits for the process to finish.
pub fn exit(&mut self) {
let mut shell_stdin = self.shell_process.stdin.as_mut().unwrap();
writeln!(&mut shell_stdin, "exit").unwrap();
shell_stdin.flush().unwrap();
self.shell_process.wait().expect("failed to wait on child");
}
}
/// Drop implementation for RootShell.
impl Drop for RootShell {
/// Exits the root shell and waits for the process to finish.
fn drop(&mut self) {
self.exit();
self.shell_process.wait().expect("failed to wait on child");
}
}
#[cfg(test)]
mod tests {
use crate::RootShell;
#[test]
fn test_custom() {
let mut root_shell = RootShell::new_custom("sudo", "bash").unwrap();
assert!(root_shell
.execute("echo Hello $USER")
.trim()
.eq("Hello root"));
}
}