From 7ba5b4599b714618efe57ed872aeeffe19ce04f3 Mon Sep 17 00:00:00 2001
From: Matt Johnston <matt@ucc.asn.au>
Date: Tue, 4 Apr 2023 21:09:29 +0800
Subject: [PATCH] Fix arg handling for more OpenSSH client compat

Is now sufficient to run "sftp -S sunsetc"
---
 async/examples/{sshclient.rs => sunsetc.rs} | 65 ++++++++++++++++++---
 1 file changed, 57 insertions(+), 8 deletions(-)
 rename async/examples/{sshclient.rs => sunsetc.rs} (83%)

diff --git a/async/examples/sshclient.rs b/async/examples/sunsetc.rs
similarity index 83%
rename from async/examples/sshclient.rs
rename to async/examples/sunsetc.rs
index 295f4d8..e5e560c 100644
--- a/async/examples/sshclient.rs
+++ b/async/examples/sunsetc.rs
@@ -4,6 +4,7 @@ use {
     log::{debug, error, info, log, trace, warn},
 };
 use anyhow::{Context, Result, anyhow, bail};
+use argh::FromArgs;
 use embassy_sync::{mutex::Mutex, blocking_mutex::raw::NoopRawMutex};
 
 use tokio::net::TcpStream;
@@ -23,7 +24,7 @@ use zeroize::Zeroizing;
 use simplelog::*;
 
 #[derive(argh::FromArgs)]
-/** con1
+/** Sunset SSH Client
  */
 struct Args {
     #[argh(switch, short='v')]
@@ -65,10 +66,60 @@ struct Args {
     #[argh(positional)]
     /// command
     cmd: Vec<String>,
+
+    // options for compatibility with sshfs, are ignored
+
+    #[allow(unused)]
+    #[argh(switch, short='x', hidden_help)]
+    /// no X11
+    no_x11: bool,
+
+    #[allow(unused)]
+    #[argh(switch, short='a', hidden_help)]
+    /// no agent forwarding
+    no_agent: bool,
+
+    #[allow(unused)]
+    #[argh(switch, short='2', hidden_help)]
+    /// ssh version 2
+    version_2: bool,
+
+    // openssh support -oThereWasNoSpace, so we preprocess that.
+    #[argh(option, short='o')]
+    /// extra options
+    option: Vec<String>,
 }
 
 fn parse_args() -> Result<Args> {
-    let mut args: Args = argh::from_env();
+    let mut in_args = std::env::args();
+
+    // OpenSSH has some quirks such as -oCommand, so we pre-process the commandline.
+    let cmd = in_args.next().expect("command name");
+    let mut mangled_args = vec![];
+
+    for a in in_args {
+        if a.starts_with("-o") {
+            let (o, v) = a.split_at(2);
+            mangled_args.push(o.to_string());
+            mangled_args.push(v.to_string());
+        } else {
+            mangled_args.push(a.to_string())
+        }
+    }
+
+    let mangled_args: Vec<&str> = mangled_args.iter().map(|i| i.as_str()).collect();
+
+    let mut args = Args::from_args(&[cmd.as_str()], mangled_args.as_slice())
+    .unwrap_or_else(|e| {
+        println!("{}", e.output);
+        std::process::exit(1)
+    });
+
+    // time crate won't read TZ if we're threaded, in case someone
+    // tries to mutate shared state with setenv.
+    // https://github.com/rust-lang/rust/issues/90308 etc
+    // logging uses the timezone, so we can't use async main.
+    setup_log(&args)?;
 
     if args.username.is_none() {
         // user@host syntax. rsplit for usernames with @ in them
@@ -81,18 +132,16 @@ fn parse_args() -> Result<Args> {
         args.username = Some(whoami::username());
     }
 
+    for o in args.option.iter() {
+        warn!("Ignoring -o {o}")
+    }
+
     Ok(args)
 }
 
 fn try_main() -> Result<()> {
     let args = parse_args()?;
 
-    // time crate won't read TZ if we're threaded, in case someone
-    // tries to mutate shared state with setenv.
-    // https://github.com/rust-lang/rust/issues/90308 etc
-    // logging uses the timezone, so we can't use async main.
-    setup_log(&args)?;
-
 
     tokio::runtime::Builder::new_current_thread()
         .enable_all()
-- 
GitLab