windows: Fix ssh reporting wrong password even it's actually correct (#38263)
Closes #34393 Currently, we’re using `zed.exe --askpass` kind of like an `nc` substitute, it prints out the SSH password to stdout with something like `println!("user-pwd")`. `ssh.exe` then reads the password from stdout so it can establish the connection. The problem is that in release builds we set `subsystem=windows` to avoid Windows spawning a black console window by default. The side effect is that `zed.exe` no longer has a stdout, so `ssh.exe` can’t read the password. Through testing, I confirmed that neither allocating a new console for `zed.exe` nor attaching it to the parent process’s stdout resolves the issue. As a result, this PR updates the implementation to use `cli.exe --askpass` instead. TODO: - [ ] Check that the `cli` path is correct on macOS - [ ] Check that the `cli` path is correct on Linux Release Notes: - N/A --------- Co-authored-by: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com>
This commit is contained in:
Generated
+1
-1
@@ -3080,6 +3080,7 @@ name = "cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askpass",
|
||||
"clap",
|
||||
"collections",
|
||||
"core-foundation 0.10.0",
|
||||
@@ -20215,7 +20216,6 @@ dependencies = [
|
||||
"agent_ui",
|
||||
"anyhow",
|
||||
"ashpd 0.11.0",
|
||||
"askpass",
|
||||
"assets",
|
||||
"assistant_tools",
|
||||
"audio",
|
||||
|
||||
@@ -85,11 +85,8 @@ impl AskPassSession {
|
||||
let askpass_script_path = temp_dir.path().join(ASKPASS_SCRIPT_NAME);
|
||||
let (askpass_opened_tx, askpass_opened_rx) = oneshot::channel::<()>();
|
||||
let listener = UnixListener::bind(&askpass_socket).context("creating askpass socket")?;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let zed_path = util::get_shell_safe_zed_path()?;
|
||||
#[cfg(target_os = "windows")]
|
||||
let zed_path = std::env::current_exe()
|
||||
.context("finding current executable path for use in askpass")?;
|
||||
let zed_cli_path =
|
||||
util::get_shell_safe_zed_cli_path().context("getting zed-cli path for askpass")?;
|
||||
|
||||
let (askpass_kill_master_tx, askpass_kill_master_rx) = oneshot::channel::<()>();
|
||||
let mut kill_tx = Some(askpass_kill_master_tx);
|
||||
@@ -137,7 +134,7 @@ impl AskPassSession {
|
||||
});
|
||||
|
||||
// Create an askpass script that communicates back to this process.
|
||||
let askpass_script = generate_askpass_script(&zed_path, &askpass_socket);
|
||||
let askpass_script = generate_askpass_script(&zed_cli_path, &askpass_socket);
|
||||
fs::write(&askpass_script_path, askpass_script)
|
||||
.await
|
||||
.with_context(|| format!("creating askpass script at {askpass_script_path:?}"))?;
|
||||
@@ -254,10 +251,10 @@ pub fn main(socket: &str) {
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) -> String {
|
||||
fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
|
||||
format!(
|
||||
"{shebang}\n{print_args} | {zed_exe} --askpass={askpass_socket} 2> /dev/null \n",
|
||||
zed_exe = zed_path,
|
||||
"{shebang}\n{print_args} | {zed_cli} --askpass={askpass_socket} 2> /dev/null \n",
|
||||
zed_cli = zed_cli_path,
|
||||
askpass_socket = askpass_socket.display(),
|
||||
print_args = "printf '%s\\0' \"$@\"",
|
||||
shebang = "#!/bin/sh",
|
||||
@@ -266,13 +263,13 @@ fn generate_askpass_script(zed_path: &str, askpass_socket: &std::path::Path) ->
|
||||
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn generate_askpass_script(zed_path: &std::path::Path, askpass_socket: &std::path::Path) -> String {
|
||||
fn generate_askpass_script(zed_cli_path: &str, askpass_socket: &std::path::Path) -> String {
|
||||
format!(
|
||||
r#"
|
||||
$ErrorActionPreference = 'Stop';
|
||||
($args -join [char]0) | & "{zed_exe}" --askpass={askpass_socket} 2> $null
|
||||
($args -join [char]0) | & "{zed_cli}" --askpass={askpass_socket} 2> $null
|
||||
"#,
|
||||
zed_exe = zed_path.display(),
|
||||
zed_cli = zed_cli_path,
|
||||
askpass_socket = askpass_socket.display(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ impl TryFrom<&str> for EncryptedPassword {
|
||||
unsafe {
|
||||
CryptProtectMemory(
|
||||
value.as_mut_ptr() as _,
|
||||
len,
|
||||
padded_length,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)?;
|
||||
}
|
||||
@@ -97,7 +97,7 @@ pub(crate) fn decrypt(mut password: EncryptedPassword) -> Result<String> {
|
||||
unsafe {
|
||||
CryptUnprotectMemory(
|
||||
password.0.as_mut_ptr() as _,
|
||||
password.1,
|
||||
password.0.len().try_into()?,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
.context("while decrypting a SSH password")?
|
||||
|
||||
@@ -22,6 +22,7 @@ default = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
clap.workspace = true
|
||||
collections.workspace = true
|
||||
ipc-channel = "0.19"
|
||||
|
||||
@@ -116,6 +116,11 @@ struct Args {
|
||||
))]
|
||||
#[arg(long)]
|
||||
uninstall: bool,
|
||||
|
||||
/// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
|
||||
/// by having Zed act like netcat communicating over a Unix socket.
|
||||
#[arg(long, hide = true)]
|
||||
askpass: Option<String>,
|
||||
}
|
||||
|
||||
fn parse_path_with_position(argument_str: &str) -> anyhow::Result<String> {
|
||||
@@ -203,6 +208,12 @@ fn main() -> Result<()> {
|
||||
}
|
||||
let args = Args::parse();
|
||||
|
||||
// `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
|
||||
if let Some(socket) = &args.askpass {
|
||||
askpass::main(socket);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Set custom data directory before any path operations
|
||||
let user_data_dir = args.user_data_dir.clone();
|
||||
if let Some(dir) = &user_data_dir {
|
||||
|
||||
+54
-3
@@ -14,7 +14,7 @@ pub mod size;
|
||||
pub mod test;
|
||||
pub mod time;
|
||||
|
||||
use anyhow::Result;
|
||||
use anyhow::{Context as _, Result};
|
||||
use futures::Future;
|
||||
use itertools::Either;
|
||||
use regex::Regex;
|
||||
@@ -290,8 +290,6 @@ fn load_shell_from_passwd() -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
/// Returns a shell escaped path for the current zed executable
|
||||
pub fn get_shell_safe_zed_path() -> anyhow::Result<String> {
|
||||
use anyhow::Context;
|
||||
|
||||
let zed_path = std::env::current_exe()
|
||||
.context("Failed to determine current zed executable path.")?
|
||||
.to_string_lossy()
|
||||
@@ -307,6 +305,59 @@ pub fn get_shell_safe_zed_path() -> anyhow::Result<String> {
|
||||
Ok(zed_path_escaped.to_string())
|
||||
}
|
||||
|
||||
/// Returns a shell escaped path for the zed cli executable, this function
|
||||
/// should be called from the zed executable, not zed-cli.
|
||||
pub fn get_shell_safe_zed_cli_path() -> Result<String> {
|
||||
let zed_path =
|
||||
std::env::current_exe().context("Failed to determine current zed executable path.")?;
|
||||
let parent = zed_path
|
||||
.parent()
|
||||
.context("Failed to determine parent directory of zed executable path.")?;
|
||||
|
||||
let possible_locations: &[&str] = if cfg!(target_os = "macos") {
|
||||
// On macOS, the zed executable and zed-cli are inside the app bundle,
|
||||
// so here ./cli is for both installed and development builds.
|
||||
&["./cli"]
|
||||
} else if cfg!(target_os = "windows") {
|
||||
// bin/zed.exe is for installed builds, ./cli.exe is for development builds.
|
||||
&["bin/zed.exe", "./cli.exe"]
|
||||
} else if cfg!(target_os = "linux") || cfg!(target_os = "freebsd") {
|
||||
// bin is the standard, ./cli is for the target directory in development builds.
|
||||
&["../bin/zed", "./cli"]
|
||||
} else {
|
||||
anyhow::bail!("unsupported platform for determining zed-cli path");
|
||||
};
|
||||
|
||||
let zed_cli_path = possible_locations
|
||||
.iter()
|
||||
.find_map(|p| {
|
||||
parent
|
||||
.join(p)
|
||||
.canonicalize()
|
||||
.ok()
|
||||
.filter(|p| p != &zed_path)
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"could not find zed-cli from any of: {}",
|
||||
possible_locations.join(", ")
|
||||
)
|
||||
})?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Ok(zed_cli_path)
|
||||
}
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
Ok(shlex::try_quote(&zed_cli_path)
|
||||
.context("Failed to shell-escape Zed executable path.")?
|
||||
.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub async fn load_login_shell_environment() -> Result<()> {
|
||||
load_shell_from_passwd().log_err();
|
||||
|
||||
@@ -25,7 +25,6 @@ agent.workspace = true
|
||||
agent_settings.workspace = true
|
||||
agent_ui.workspace = true
|
||||
anyhow.workspace = true
|
||||
askpass.workspace = true
|
||||
assets.workspace = true
|
||||
assistant_tools.workspace = true
|
||||
audio.workspace = true
|
||||
|
||||
@@ -176,12 +176,6 @@ pub fn main() {
|
||||
return;
|
||||
}
|
||||
|
||||
// `zed --askpass` Makes zed operate in nc/netcat mode for use with askpass
|
||||
if let Some(socket) = &args.askpass {
|
||||
askpass::main(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
// `zed --nc` Makes zed operate in nc/netcat mode for use with MCP
|
||||
if let Some(socket) = &args.nc {
|
||||
match nc::main(socket) {
|
||||
@@ -1249,11 +1243,6 @@ struct Args {
|
||||
#[arg(long)]
|
||||
system_specs: bool,
|
||||
|
||||
/// Used for SSH/Git password authentication, to remove the need for netcat as a dependency,
|
||||
/// by having Zed act like netcat communicating over a Unix socket.
|
||||
#[arg(long, hide = true)]
|
||||
askpass: Option<String>,
|
||||
|
||||
/// Used for the MCP Server, to remove the need for netcat as a dependency,
|
||||
/// by having Zed act like netcat communicating over a Unix socket.
|
||||
#[arg(long, hide = true)]
|
||||
|
||||
Reference in New Issue
Block a user