mod error;
#[cfg(target_family = "windows")]
use error::Error;
use error::Result;
pub use ssh_agent_client_rs::Client;
pub use ssh_agent_client_rs::Identity;
pub trait GitBash {
fn connect_to_git_bash_or_linux(path: &std::path::Path) -> Result<Client>;
}
impl GitBash for Client {
#[cfg(target_family = "windows")]
fn connect_to_git_bash_or_linux(path: &std::path::Path) -> Result<Client> {
let (tcp_address, key_guid) = read_and_parse_fake_socket_file(path)?;
let mut tcp_stream = std::net::TcpStream::connect(&tcp_address)?;
do_secret_handshake_with_remote_end(&key_guid, &mut tcp_stream)?;
Ok(Client::with_read_write(Box::new(tcp_stream)))
}
#[cfg(target_family = "unix")]
fn connect_to_git_bash_or_linux(path: &std::path::Path) -> Result<Client> {
Ok(Client::connect(path)?)
}
}
#[cfg(target_family = "windows")]
fn read_and_parse_fake_socket_file(path: &std::path::Path) -> Result<(String, String)> {
let conn_string = std::fs::read_to_string(path)?;
let (tcp_address, key_guid) = parse_fake_socket_metadata(&conn_string)?;
Ok((tcp_address, key_guid))
}
#[cfg(target_family = "windows")]
fn do_secret_handshake_with_remote_end(key_guid: &str, tcp_stream: &mut std::net::TcpStream) -> Result<()> {
use std::io::{Read, Write};
let b1 = parse_guid_and_change_byte_order(key_guid)?;
let _amount = tcp_stream.write(&b1)?;
let mut b2: [u8; 16] = [0; 16];
let _amount = tcp_stream.read(&mut b2)?;
let pid_uid_gid = prepare_pid_uid_gid()?;
let _amount = tcp_stream.write(&pid_uid_gid)?;
let mut b3: [u8; 16] = [0; 16];
let _amount = tcp_stream.read(&mut b3)?;
Ok(())
}
#[cfg(target_family = "windows")]
fn parse_fake_socket_metadata(conn_string: &str) -> Result<(String, String)> {
let conn_string = conn_string.trim_start_matches("!<socket >").trim_end_matches("\0");
let mut split_iter = conn_string.split_whitespace();
let tcp_port = split_iter
.next()
.ok_or_else(|| Error::GitBashErrorMessage("Bad format in ssh agent connection file.".to_string()))?;
let is_cygwin = split_iter
.next()
.ok_or_else(|| Error::GitBashErrorMessage("Bad format in ssh agent connection file.".to_string()))?;
let key_guid = split_iter
.next()
.ok_or_else(|| Error::GitBashErrorMessage("Bad format in ssh agent connection file.".to_string()))?;
if is_cygwin != "s" {
return Err(Error::GitBashErrorMessage(
"Old version of MSysGit ssh-agent implementation is not supported.".to_string(),
));
}
let tcp_address = format!("localhost:{}", tcp_port);
Ok((tcp_address, key_guid.to_string()))
}
#[cfg(target_family = "windows")]
fn prepare_pid_uid_gid() -> Result<[u8; 12]> {
let pid: u32 = std::process::id();
let uid = get_uid()?;
let gid = pid;
let pid_uid_gid = order_bytes_pid_uid_gid(pid, uid, gid).unwrap();
Ok(pid_uid_gid)
}
#[cfg(target_family = "windows")]
fn get_uid() -> Result<u32> {
let vec_byte_out = std::process::Command::new(r#"C:\Program Files\Git\usr\bin\bash.exe"#)
.arg("-c")
.arg("ps")
.output()?
.stdout;
let string_output = String::from_utf8_lossy(&vec_byte_out);
let uid = parse_uid(string_output)?;
Ok(uid)
}
#[cfg(target_family = "windows")]
fn order_bytes_pid_uid_gid(pid: u32, uid: u32, gid: u32) -> Result<[u8; 12]> {
let mut pid_uid_gid: [u8; 12] = [0; 12];
pid_uid_gid[0..4].swap_with_slice(&mut pid.to_le_bytes());
pid_uid_gid[4..8].swap_with_slice(&mut uid.to_le_bytes());
pid_uid_gid[8..12].swap_with_slice(&mut gid.to_le_bytes());
Ok(pid_uid_gid)
}
#[cfg(target_family = "windows")]
fn parse_uid(string_output: std::borrow::Cow<'_, str>) -> Result<u32> {
let mut lines = string_output.lines();
let _line_0 = lines
.next()
.ok_or_else(|| Error::GitBashErrorMessage("Command 'ps' did not return correct list.".to_string()))?;
let line_1 = lines
.next()
.ok_or_else(|| Error::GitBashErrorMessage("Command 'ps' did not return correct list.".to_string()))?;
let mut columns = line_1.split_ascii_whitespace();
let uid: u32 = columns
.nth(5)
.ok_or_else(|| Error::GitBashErrorMessage("Command 'ps' did not return correct list.".to_string()))?
.parse()
.map_err(|_| Error::GitBashErrorMessage("Format of 'bash.exe -c ps' is incorrect.".to_string()))?;
Ok(uid)
}
#[cfg(target_family = "windows")]
fn parse_guid_and_change_byte_order(key_guid: &str) -> Result<[u8; 16]> {
let group0 = u32::from_str_radix(&key_guid[0..8], 16)
.map_err(|_| Error::GitBashErrorMessage("Guid in SSH_AUTH_SOCK is incorrect.".to_string()))?;
let group1 = u32::from_str_radix(&key_guid[9..17], 16)
.map_err(|_| Error::GitBashErrorMessage("Guid in SSH_AUTH_SOCK is incorrect.".to_string()))?;
let group2 = u32::from_str_radix(&key_guid[18..26], 16)
.map_err(|_| Error::GitBashErrorMessage("Guid in SSH_AUTH_SOCK is incorrect.".to_string()))?;
let group3 = u32::from_str_radix(&key_guid[27..35], 16)
.map_err(|_| Error::GitBashErrorMessage("Guid in SSH_AUTH_SOCK is incorrect.".to_string()))?;
let mut b1: [u8; 16] = [0; 16];
b1[0..4].swap_with_slice(&mut group0.to_le_bytes());
b1[4..8].swap_with_slice(&mut group1.to_le_bytes());
b1[8..12].swap_with_slice(&mut group2.to_le_bytes());
b1[12..16].swap_with_slice(&mut group3.to_le_bytes());
Ok(b1)
}
#[cfg(test)]
mod tests {
#[cfg(target_family = "windows")]
use super::*;
#[test]
#[cfg(target_family = "windows")]
fn test_parse_fake_socket_metadata() {
let conn_string = "!<socket >49722 s 09B97624-72E2CDC5-38596B86-E9F0B690\0";
let (tcp_address, key_guid) = parse_fake_socket_metadata(conn_string).unwrap();
assert_eq!(tcp_address, "localhost:49722");
assert_eq!(key_guid, "09B97624-72E2CDC5-38596B86-E9F0B690");
}
#[test]
#[cfg(target_family = "windows")]
fn test_parse_uid() {
let string_output = r#" PID PPID PGID WINPID TTY UID STIME COMMAND
1344 1 1344 15352 ? 197610 13:36:43 /usr/bin/ssh-agent
2542 1 2542 21776 cons1 197610 19:09:45 /usr/bin/bash
"#;
let uid = parse_uid(string_output.into()).unwrap();
assert_eq!(uid, 197610);
}
#[test]
#[cfg(target_family = "windows")]
fn test_parse_guid_and_change_byte_order() {
let guid = "09B97624-72E2CDC5-38596B86-E9F0B690";
let ordered_guid = parse_guid_and_change_byte_order(guid).unwrap();
let compare_with: [u8; 16] = [36, 118, 185, 9, 197, 205, 226, 114, 134, 107, 89, 56, 144, 182, 240, 233];
assert_eq!(ordered_guid, compare_with);
}
#[test]
#[cfg(target_family = "windows")]
fn test_order_bytes_pid_uid_gid() {
let pid_uid_gid = order_bytes_pid_uid_gid(1, 2, 3).unwrap();
let compare_with: [u8; 12] = [1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0];
assert_eq!(pid_uid_gid, compare_with);
}
}