Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add windows askpass named pipe support for fetch/pull fork/exec implementation #3262

Merged
merged 5 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 51 additions & 17 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions gitbutler-git/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ path = "src/lib.rs"

[[bin]]
name = "gitbutler-git-askpass"
path = "src/cli/bin/askpass.rs"
path = "src/bin/askpass.rs"

[[bin]]
name = "gitbutler-git-setsid"
path = "src/cli/bin/setsid.rs"
path = "src/bin/setsid.rs"

[features]
default = ["serde", "tokio"]
Expand All @@ -29,3 +29,8 @@ sysinfo = "0.30.5"

[target."cfg(unix)".dependencies]
nix = { version = "0.27.1", features = ["process", "socket", "user"] }

[target."cfg(windows)".dependencies]
winapi = { version = "0.3.9", features = ["winbase", "namedpipeapi"] }
# synchronous named pipes for the askpass utility
windows-named-pipe = "0.1.0"
2 changes: 0 additions & 2 deletions gitbutler-git/src/backend.rs

This file was deleted.

49 changes: 49 additions & 0 deletions gitbutler-git/src/bin/askpass.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#[cfg(unix)]
#[path = "askpass/unix.rs"]
mod unix;
#[cfg(windows)]
#[path = "askpass/windows.rs"]
mod windows;

#[cfg(windows)]
use self::windows::UnixCompatibility;

use std::io::{BufRead, BufReader, BufWriter, Write};

pub fn main() {
let pipe_name = std::env::var("GITBUTLER_ASKPASS_PIPE").expect("do not run this binary yourself; it's only meant to be run by GitButler (missing GITBUTLER_ASKPASS_PIPE env var)");
let pipe_secret = std::env::var("GITBUTLER_ASKPASS_SECRET").expect("do not run this binary yourself; it's only meant to be run by GitButler (missing GITBUTLER_ASKPASS_SECRET env var)");
let prompt = std::env::args().nth(1).expect("do not run this binary yourself; it's only meant to be run by GitButler (missing prompt arg)");

#[cfg(unix)]
let raw_stream = self::unix::establish(&pipe_name);
#[cfg(windows)]
let raw_stream = self::windows::establish(&pipe_name);

let mut reader = BufReader::new(raw_stream.try_clone().unwrap());
let mut writer = BufWriter::new(raw_stream.try_clone().unwrap());

// Write the secret.
writeln!(writer, "{pipe_secret}").expect("write(secret):");

// Write the prompt that Git gave us.
writeln!(writer, "{prompt}").expect("write(prompt):");

writer.flush().expect("flush():");

// Clear the timeout (it's now time for the user to provide a response)
raw_stream
.set_read_timeout(None)
.expect("set_read_timeout(None):");

// Wait for the response.
let mut password = String::new();
let nread = reader.read_line(&mut password).expect("read_line():");
if nread == 0 {
panic!("read_line() returned 0");
}

// Write the response back to Git.
// `password` already has a newline at the end.
write!(std::io::stdout(), "{password}").expect("write(password):");
}
5 changes: 5 additions & 0 deletions gitbutler-git/src/bin/askpass/unix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use std::os::unix::net::UnixStream;

pub fn establish(sock_path: &str) -> UnixStream {
UnixStream::connect(sock_path).expect("connect():")
}
59 changes: 59 additions & 0 deletions gitbutler-git/src/bin/askpass/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::{
io,
os::windows::io::{AsRawHandle, FromRawHandle},
time::Duration,
};
use windows_named_pipe::PipeStream;

pub fn establish(sock_path: &str) -> PipeStream {
PipeStream::connect(sock_path).unwrap()
}

/// There are some methods we need in order to run askpass correctly,
/// and those methods are not available out of the box on windows.
/// We stub them using this trait so we don't have to newtype
/// the pipestream itself (which would be extensive and un-DRY).
pub trait UnixCompatibility: Sized {
fn try_clone(&self) -> Option<Self>;
fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()>;
}

impl UnixCompatibility for PipeStream {
fn try_clone(&self) -> Option<Self> {
Some(unsafe { Self::from_raw_handle(self.as_raw_handle()) })
}

fn set_read_timeout(&self, timeout: Option<Duration>) -> io::Result<()> {
// NOTE(qix-): Technically, this shouldn't work (and probably doesn't).
// NOTE(qix-): The documentation states:
// NOTE(qix-):
// NOTE(qix-): > This parameter must be NULL if . . . client and server
// NOTE(qix-): > processes are on the same computer.
// NOTE(qix-):
// NOTE(qix-): This is indeed the case here, but we try to make it work
// NOTE(qix-): anyway.
#[allow(unused_assignments)]
let mut timeout_ms: winapi::shared::minwindef::DWORD = 0;
let timeout_ptr: winapi::shared::minwindef::LPDWORD = if let Some(timeout) = timeout {
timeout_ms = timeout.as_millis() as winapi::shared::minwindef::DWORD;
&mut timeout_ms as *mut _
} else {
std::ptr::null_mut()
};

let r = unsafe {
winapi::um::namedpipeapi::SetNamedPipeHandleState(
self.as_raw_handle(),
std::ptr::null_mut(),
std::ptr::null_mut(),
timeout_ptr,
)
};

if r == 0 {
Err(io::Error::last_os_error())
} else {
Ok(())
}
}
}
10 changes: 10 additions & 0 deletions gitbutler-git/src/bin/setsid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// NOTE(qix-): Cargo doesn't let us specify binaries based on the platform,
// NOTE(qix-): unfortunately. This utility is not used on Windows but is
// NOTE(qix-): build anyway. We'll address this at a later time.
// NOTE(qix-):
// NOTE(qix-): For now, we just stub out the main function on windows and panic.

#[cfg(unix)]
include!("setsid/unix.rs");
#[cfg(windows)]
include!("setsid/windows.rs");
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
#[cfg(not(target_os = "windows"))]
use nix::{
libc::{c_int, wait, EXIT_FAILURE, WEXITSTATUS, WIFEXITED, WIFSIGNALED, WTERMSIG},
unistd::{fork, setsid, ForkResult},
};
use std::{os::unix::process::CommandExt, process};

#[cfg(target_os = "windows")]
pub fn main() {
panic!("This binary is only meant to be run on Unix-like systems. It exists on Windows only because Cargo cannot switch off bins based on target platform.");
}

#[cfg(not(target_os = "windows"))]
pub fn main() {
let has_pipe_var = std::env::var("GITBUTLER_ASKPASS_PIPE")
.map(|v| !v.is_empty())
Expand Down
3 changes: 3 additions & 0 deletions gitbutler-git/src/bin/setsid/windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub fn main() {
panic!("This binary is only meant to be run on Unix-like systems. It exists on Windows only because Cargo cannot switch off bins based on target platform.");
}
Loading