diff --git a/src/cli.rs b/src/cli.rs index afeabf0..74e7db8 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -28,6 +28,10 @@ pub struct ClientCli { /// Suffix identifying a still-growing file. #[arg(long, default_value = "partial")] pub extension: String, + + /// Path to the scpcap executable on the remote host, e.g. `~/bin/scpcap`. + #[arg(long, default_value = "scpcap")] + pub remote_exe: String, } /// Remote helper mode, ssh-spawned by the client (like `rsync --server`). diff --git a/src/client.rs b/src/client.rs index 8227cae..eb293f3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -61,7 +61,7 @@ pub fn run(cli: ClientCli) -> Result<()> { "--dest-temp".to_string(), dest.temp_path.clone(), ]; - let remote_cmd = build_server_command(&server_args); + let remote_cmd = build_server_command(&cli.remote_exe, &server_args); let mut child = spawn_remote_server(host, &remote_cmd)?; let stdin = child.stdin.take().expect("piped stdin"); @@ -103,7 +103,7 @@ pub fn run(cli: ClientCli) -> Result<()> { server_args.push("--compress".to_string()); server_args.push("zstd".to_string()); } - let remote_cmd = build_server_command(&server_args); + let remote_cmd = build_server_command(&cli.remote_exe, &server_args); let mut child = spawn_remote_server(host, &remote_cmd)?; let stdout = child.stdout.take().expect("piped stdout"); diff --git a/src/ssh.rs b/src/ssh.rs index c1a679b..0e71703 100644 --- a/src/ssh.rs +++ b/src/ssh.rs @@ -4,9 +4,13 @@ use std::process::{Child, Command, Stdio}; /// Builds the argv for the remote `scpcap --server` invocation, run as a /// single ssh remote-command argument (so it goes through the remote shell, -/// same as rsync's `ssh host rsync --server ...`). -pub fn build_server_command(args: &[String]) -> String { - let mut parts = vec!["scpcap".to_string(), "--server".to_string()]; +/// same as rsync's `ssh host rsync --server ...`). `remote_exe` is the path +/// (or bare name, resolved via remote `PATH`) to the scpcap executable on +/// the remote host, e.g. `~/bin/scpcap`. Like rsync's `--rsync-path`, it's +/// passed through unquoted so `~` and `$HOME`-style remote shell expansions +/// work; it's a trusted, locally-supplied flag, not remote-controlled data. +pub fn build_server_command(remote_exe: &str, args: &[String]) -> String { + let mut parts = vec![remote_exe.to_string(), "--server".to_string()]; parts.extend(args.iter().map(|a| shell_quote(a))); parts.join(" ") } @@ -31,14 +35,23 @@ mod tests { #[test] fn builds_quoted_command() { - let cmd = build_server_command(&[ - "--recv".to_string(), - "--dest-final".to_string(), - "/a b/c.pcap".to_string(), - ]); + let cmd = build_server_command( + "scpcap", + &[ + "--recv".to_string(), + "--dest-final".to_string(), + "/a b/c.pcap".to_string(), + ], + ); assert_eq!( cmd, "scpcap --server '--recv' '--dest-final' '/a b/c.pcap'" ); } + + #[test] + fn builds_command_with_custom_remote_exe() { + let cmd = build_server_command("~/bin/scpcap", &["--recv".to_string()]); + assert_eq!(cmd, "~/bin/scpcap --server '--recv'"); + } }