//! One noninteractive SSH command with host-mediated authentication. use clap::Parser; use std::{ io::{self, Read, Write}, time::{Duration, Instant}, }; #[derive(Parser)] struct Args { #[arg(short = 'p', default_value_t = 22)] port: u16, #[arg(short = 'l')] user: Option, #[arg(short = 'o')] options: Vec, host: String, #[arg(required = true, trailing_var_arg = true)] command: Vec, } fn run(args: Args) -> io::Result { let mut accept_new = false; for option in args.options { match option.as_str() { "StrictHostKeyChecking=accept-new" => accept_new = true, "StrictHostKeyChecking=yes" => accept_new = false, _ => return Err(io::Error::other("unsupported SSH option")), } } let (user, host) = match args.host.split_once('@') { Some((user, host)) => (Some(user), host), None => (args.user.as_deref(), args.host.as_str()), }; let session = sausage_ssh::identity::connect(host, args.port, user, accept_new)?; let mut input = Vec::new(); io::stdin().read_to_end(&mut input)?; let output = session.execute_input( args.command.join(" ").as_bytes(), &input, usize::MAX, Instant::now() + Duration::from_secs(300), )?; io::stdout().write_all(&output.stdout)?; io::stderr().write_all(&output.stderr)?; Ok(output.exit_code.unwrap_or(255)) } fn main() { let status = match run(Args::parse()) { Ok(status) => status, Err(error) => { eprintln!("{error}"); 255 } }; std::process::exit(status.min(255) as i32); }