use std::process::{Command, Stdio};
use std::io::Write;
use clap::Parser;
#[derive(Parser)]
#[command(name = "qsshpass")]
#[command(about = "Non-interactive password provider for QSSH (INSECURE - testing only!)")]
struct Args {
#[arg(short, long)]
password: Option<String>,
#[arg(short = 'f', long)]
password_file: Option<String>,
#[arg(short = 'e', long)]
password_env: Option<String>,
#[arg(trailing_var_arg = true, required = true)]
command: Vec<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let password = if let Some(p) = args.password {
eprintln!("WARNING: Using password from command line is visible in ps!");
p
} else if let Some(file) = args.password_file {
std::fs::read_to_string(file)?.trim().to_string()
} else if let Some(env_var) = args.password_env {
std::env::var(env_var)?
} else {
eprintln!("ERROR: Must specify -p, -f, or -e for password source");
std::process::exit(1);
};
if !args.command[0].contains("qssh") {
eprintln!("WARNING: qsshpass is designed for qssh, not {}", args.command[0]);
}
let mut child = Command::new(&args.command[0])
.args(&args.command[1..])
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.env("QSSH_PASSWORD", &password) .spawn()?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(password.as_bytes())?;
stdin.write_all(b"\n")?;
}
let status = child.wait()?;
std::process::exit(status.code().unwrap_or(1));
}
#[cfg(test)]
mod tests {
#[test]
fn test_security_warning() {
let env_var_name = "QSSH_PASSWORD";
assert!(!env_var_name.is_empty(), "password env var name must not be empty");
assert!(env_var_name.starts_with("QSSH_"), "password env var should use QSSH_ prefix");
}
}