@@ -315,7 +315,8 @@ impl RemoteConnection for SshRemoteConnection {
*ssh_path_style,
ssh_shell,
*ssh_shell_kind,
- socket.ssh_args(),
+ socket.ssh_command_options(),
+ &socket.connection_options.ssh_destination(),
interactive,
)
} else {
@@ -329,7 +330,8 @@ impl RemoteConnection for SshRemoteConnection {
*ssh_path_style,
ssh_shell,
*ssh_shell_kind,
- socket.ssh_args(),
+ socket.ssh_command_options(),
+ &socket.connection_options.ssh_destination(),
interactive,
)
}
@@ -340,12 +342,13 @@ impl RemoteConnection for SshRemoteConnection {
forwards: Vec<(u16, String, u16)>,
) -> Result<CommandTemplate> {
let Self { socket, .. } = self;
- let mut args = socket.ssh_args();
+ let mut args = socket.ssh_command_options();
args.push("-N".into());
for (local_port, host, remote_port) in forwards {
args.push("-L".into());
args.push(format!("{local_port}:{host}:{remote_port}"));
}
+ args.push(socket.connection_options.ssh_destination());
Ok(CommandTemplate {
program: "ssh".into(),
args,
@@ -1208,20 +1211,23 @@ impl SshSocket {
cmd
}
- // On Windows, we need to use `SSH_ASKPASS` to provide the password to ssh.
- // On Linux, we use the `ControlPath` option to create a socket file that ssh can use to
- fn ssh_args(&self) -> Vec<String> {
- let mut arguments = self.connection_options.additional_args();
+ // Returns the SSH command-line options (without the destination) for building commands.
+ // On Linux, this includes the ControlPath option to reuse the existing connection.
+ // Note: The destination must be added separately after all options to ensure proper
+ // SSH command structure: ssh [options] destination [command]
+ fn ssh_command_options(&self) -> Vec<String> {
+ let arguments = self.connection_options.additional_args();
#[cfg(not(windows))]
- arguments.extend(vec![
- "-o".to_string(),
- "ControlMaster=no".to_string(),
- "-o".to_string(),
- format!("ControlPath={}", self.socket_path.display()),
- self.connection_options.ssh_destination(),
- ]);
- #[cfg(windows)]
- arguments.push(self.connection_options.ssh_destination());
+ let arguments = {
+ let mut args = arguments;
+ args.extend(vec![
+ "-o".to_string(),
+ "ControlMaster=no".to_string(),
+ "-o".to_string(),
+ format!("ControlPath={}", self.socket_path.display()),
+ ]);
+ args
+ };
arguments
}
@@ -1571,7 +1577,8 @@ fn build_command_posix(
ssh_path_style: PathStyle,
ssh_shell: &str,
ssh_shell_kind: ShellKind,
- ssh_args: Vec<String>,
+ ssh_options: Vec<String>,
+ ssh_destination: &str,
interactive: Interactive,
) -> Result<CommandTemplate> {
use std::fmt::Write as _;
@@ -1632,7 +1639,7 @@ fn build_command_posix(
};
let mut args = Vec::new();
- args.extend(ssh_args);
+ args.extend(ssh_options);
if let Some((local_port, host, remote_port)) = port_forward {
args.push("-L".into());
@@ -1648,6 +1655,8 @@ fn build_command_posix(
// -T disables pseudo-TTY allocation (for non-interactive piped stdio)
Interactive::No => args.push("-T".into()),
}
+ // The destination must come after all options but before the command
+ args.push(ssh_destination.into());
args.push(exec);
Ok(CommandTemplate {
@@ -1667,7 +1676,8 @@ fn build_command_windows(
ssh_path_style: PathStyle,
ssh_shell: &str,
_ssh_shell_kind: ShellKind,
- ssh_args: Vec<String>,
+ ssh_options: Vec<String>,
+ ssh_destination: &str,
interactive: Interactive,
) -> Result<CommandTemplate> {
use base64::Engine as _;
@@ -1719,7 +1729,7 @@ fn build_command_windows(
};
let mut args = Vec::new();
- args.extend(ssh_args);
+ args.extend(ssh_options);
if let Some((local_port, host, remote_port)) = port_forward {
args.push("-L".into());
@@ -1736,6 +1746,9 @@ fn build_command_windows(
Interactive::No => args.push("-T".into()),
}
+ // The destination must come after all options but before the command
+ args.push(ssh_destination.into());
+
// Windows OpenSSH server incorrectly escapes the command string when the PTY is used.
// The simplest way to work around this is to use a base64 encoded command, which doesn't require escaping.
let utf16_bytes: Vec<u16> = exec.encode_utf16().collect();
@@ -1774,6 +1787,7 @@ mod tests {
"/bin/bash",
ShellKind::Posix,
vec!["-o".to_string(), "ControlMaster=auto".to_string()],
+ "user@host",
Interactive::No,
)?;
assert_eq!(command.program, "ssh");
@@ -1793,6 +1807,7 @@ mod tests {
"/bin/fish",
ShellKind::Fish,
vec!["-p".to_string(), "2222".to_string()],
+ "user@host",
Interactive::Yes,
)?;
@@ -1804,6 +1819,7 @@ mod tests {
"2222",
"-q",
"-t",
+ "user@host",
"cd \"$HOME/work\" && exec env INPUT_VA=val remote_program arg1 arg2"
]
);
@@ -1825,6 +1841,7 @@ mod tests {
"/bin/fish",
ShellKind::Fish,
vec!["-p".to_string(), "2222".to_string()],
+ "user@host",
Interactive::Yes,
)?;
@@ -1838,6 +1855,7 @@ mod tests {
"1:foo:2",
"-q",
"-t",
+ "user@host",
"cd && exec env INPUT_VA=val /bin/fish -l"
]
);