use crate::error::RegentError;
use crate::hosts::command::CommandResult;
use crate::hosts::handlers::HostHandler;
use crate::hosts::privilege::Credentials;
use crate::hosts::privilege::Privilege;
use crate::secrets::SecretProvider;
use std::process::Stdio;
use tokio::io::AsyncWriteExt;
use crate::hosts::handlers::shell_quote;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tokio::process::Command;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalHostHandler {
pub user: WhichUser,
}
impl LocalHostHandler {
pub fn from(user: WhichUser) -> Self {
Self { user }
}
}
impl HostHandler for LocalHostHandler {
async fn connect(
&mut self,
_endpoint: &str,
) -> Result<(), RegentError> {
Ok(())
}
async fn is_connected(&mut self) -> bool {
true
}
async fn disconnect(&mut self) -> Result<(), RegentError> {
Ok(())
}
async fn is_this_command_available(
&mut self,
command: &str,
privilege: &Privilege,
) -> Result<bool, RegentError> {
let check_cmd_content = format!("command -v {}", shell_quote(command));
let check_cmd_result = self
.run_command(check_cmd_content.as_str(), &Privilege::None)
.await;
match check_cmd_result {
Ok(cmd_result) => {
if cmd_result.return_code == 0 {
Ok(true)
} else {
Ok(false)
}
}
Err(e) => {
return Err(RegentError::FailureToRunCommand(format!("{:?}", e)));
}
}
}
async fn run_command(
&mut self,
command: &str,
privilege: &Privilege,
) -> Result<CommandResult, RegentError> {
let result = match (privilege, &self.user) {
(Privilege::None, _) => {
Command::new("sh")
.arg("-c")
.arg(command)
.kill_on_drop(true)
.output()
.await
}
(Privilege::WithSudo | Privilege::WithSudoRs, WhichUser::CurrentUser) => {
Command::new(match privilege {
Privilege::WithSudoRs => "sudo-rs",
_ => "sudo",
})
.arg("-n")
.arg("sh")
.arg("-c")
.arg(command)
.env_remove("DISPLAY")
.env_remove("WAYLAND_DISPLAY")
.env_remove("DBUS_SESSION_BUS_ADDRESS")
.env_remove("XDG_RUNTIME_DIR")
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.await
}
(Privilege::WithSudo, WhichUser::CurrentUserWithSudoPassword(password)) => {
run_with_sudo("sudo", password, command).await
}
(Privilege::WithSudoRs, WhichUser::CurrentUserWithSudoPassword(password)) => {
run_with_sudo("sudo-rs", password, command).await
}
(Privilege::WithSudo, WhichUser::UsernamePassword(credentials)) => {
run_with_sudo("sudo", credentials.password(), command).await
}
(Privilege::WithSudoRs, WhichUser::UsernamePassword(credentials)) => {
run_with_sudo("sudo-rs", credentials.password(), command).await
}
};
match result {
Ok(output) => match output.status.code() {
Some(code) => Ok(CommandResult {
return_code: code.into(),
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
}),
None => Err(RegentError::FailureToRunCommand(format!(
"Process terminated by a signal : {:?}",
output
))),
},
Err(e) => Err(RegentError::FailureToRunCommand(format!("{}", e))),
}
}
#[cfg(feature = "windows")]
async fn run_windows_command(&mut self, command: &str) -> Result<CommandResult, RegentError> {
match Command::new("cmd")
.args(&["/C", command])
.kill_on_drop(true)
.output()
.await
{
Ok(output) => match output.status.code() {
Some(code) => Ok(CommandResult {
return_code: code.into(),
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
}),
None => {
Err(RegentError::FailureToRunCommand(format!(
"Process terminated by a signal : {:?}",
output
)))
}
},
Err(e) => Err(RegentError::FailureToRunCommand(format!("{}", e))),
}
}
async fn get_file(&mut self, path: PathBuf) -> Result<Vec<u8>, RegentError> {
if !self.is_connected().await {
return Err(RegentError::FailedInitialization(
"Not connected to host".to_string(),
));
}
match std::fs::read(path) {
Ok(file_content) => Ok(file_content),
Err(details) => {
return Err(RegentError::FailureToRunCommand(format!("{:?}", details)));
}
}
}
}
#[derive(Clone, Serialize, Deserialize)]
pub enum WhichUser {
CurrentUser,
CurrentUserWithSudoPassword(String),
UsernamePassword(Credentials),
}
impl std::fmt::Debug for WhichUser {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WhichUser::CurrentUser => write!(f, "CurrentUser"),
WhichUser::CurrentUserWithSudoPassword(_) => {
write!(f, "CurrentUserWithSudoPassword(*REDACTED*)")
}
WhichUser::UsernamePassword(credentials) => {
write!(f, "UsernamePassword({:?})", credentials)
}
}
}
}
async fn run_with_sudo(
sudo_binary: &str,
password: &str,
command: &str,
) -> Result<std::process::Output, std::io::Error> {
let mut child = Command::new(sudo_binary)
.arg("-S") .arg("sh")
.arg("-c")
.arg(command)
.env_remove("DISPLAY")
.env_remove("WAYLAND_DISPLAY")
.env_remove("DBUS_SESSION_BUS_ADDRESS")
.env_remove("XDG_RUNTIME_DIR")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(password.as_bytes()).await?;
stdin.write_all(b"\n").await?;
}
child.wait_with_output().await
}