diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2023-01-23 14:29:31 +0100 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2023-01-23 14:55:49 +0100 |
commit | f35bde57ae4093665a1e54a8db80ac810ac061d8 (patch) | |
tree | 740e04f8cc4ffd054c1e6a75d3752bd9df35dcab | |
parent | d7903cb732c9596658085ee469ecea9afa83b1be (diff) |
ipc: Only build the Unix Domain Socket emulation code on Windows.
- This fixes a test failure on big endian systems. No big endian
Windows systems are known to exist.
-rw-r--r-- | ipc/src/assuan/socket.rs | 175 | ||||
-rw-r--r-- | ipc/src/assuan/socket/windows.rs | 167 |
2 files changed, 177 insertions, 165 deletions
diff --git a/ipc/src/assuan/socket.rs b/ipc/src/assuan/socket.rs index 3084f935..2b2d21e1 100644 --- a/ipc/src/assuan/socket.rs +++ b/ipc/src/assuan/socket.rs @@ -2,18 +2,14 @@ //! //! [assuan-socket.c]: https://github.com/gpg/libassuan/blob/master/src/assuan-socket.c -// Makes writing platform-specific code less verbose (less #[cfgs] everywhere) -#![allow(dead_code, unused_imports)] - -use std::io::{Write, Read}; use std::path::Path; -use std::fs::File; - -use anyhow::anyhow; use crate::Result; #[cfg(windows)] +mod windows; + +#[cfg(windows)] pub(crate) type IpcStream = tokio::net::TcpStream; #[cfg(unix)] pub(crate) type IpcStream = tokio::net::UnixStream; @@ -34,8 +30,15 @@ pub(crate) fn sock_connect(path: impl AsRef<Path>) -> Result<IpcStream> { Ok(tokio::net::UnixStream::from_std(stream)?) }, windows => { + use std::io::{Write, Read}; use std::net::{Ipv4Addr, TcpStream}; + use windows::{ + read_port_and_nonce, + Rendezvous, + UdsEmulation, + }; + let rendezvous = read_port_and_nonce(path.as_ref())?; let Rendezvous { port, uds_emulation, nonce } = rendezvous; @@ -73,161 +76,3 @@ pub(crate) fn sock_connect(path: impl AsRef<Path>) -> Result<IpcStream> { }, } } - -/// Socket connection data. -#[derive(Debug)] -struct Rendezvous { - port: u16, - uds_emulation: UdsEmulation, - nonce: [u8; 16], -} - -/// Unix domain socket emulation type (Windows only). -/// -/// Until Windows 10 Update 1803, Windows did not support native UNIX domain -/// sockets. To work around that, developers historically used TCP (readily -/// available) connection coupled with an authentication nonce (or "cookie"). -#[derive(Debug)] -enum UdsEmulation { - /// Cygwin socket emulation. - /// - /// File format: `!<socket >%u %c %08x-%08x-%08x-%08x` (scanf style) - /// %u: local TCP port - /// %c: socket type ("s" for `SOCK_STREAM`, "d" for `SOCK_DGRAM`) - /// %08x-%08x-%08x-%08x: authentication nonce - /// - /// Starting with client, both sides first exchange the 16-byte authentication - /// nonce, after which they exchange `ucred` structure (socket.h). - Cygwin, - /// Libassuan's custom socket emulation. - /// - /// File format: `<PORT>\n<NONCE>` - /// PORT: textual local TCP port (e.g. "12345") - /// NONCE: raw 16-byte authentication nonce - /// - /// After connecting, client has to authenticate itself by sending the - /// 16-byte authentication nonce. - Libassuan, -} - -/// Reads socket connection info from a Windows file emulating a Unix socket. -/// -/// Inspired by `read_port_and nonce` from assuan-socket.c. -fn read_port_and_nonce(fname: &Path) -> Result<Rendezvous> { - let mut file = File::open(fname)?; - // Socket connection info will be in either a <= 54 byte long Cygwin format - // or ~5+1+16 (modulo whitespace separators) custom libassuan format - let mut contents = Vec::with_capacity(64); - file.read_to_end(&mut contents)?; - - read_port_and_nonce_from_string(&contents) -} - -fn read_port_and_nonce_from_string(contents: &[u8]) -> Result<Rendezvous> { - let maybe_utf8 = std::str::from_utf8(contents).ok(); - - match maybe_utf8.and_then(|buf| buf.strip_prefix("!<socket >")) { - // libassuan's Cygwin compatible socket emulation. - // Format: "!<socket >%u %c %08x-%08x-%08x-%08x\x00" (scanf-like) - Some(buf) => { - let opt_skip_nul = buf.strip_suffix('\x00').unwrap_or(buf); - // Split into parts: port, kind of socket and nonce - let mut iter = opt_skip_nul.split_terminator(' '); - match (iter.next(), iter.next(), iter.next()) { - (Some(port), Some("s"), Some(nonce)) => { - let port = port.parse()?; - - // This is wasteful but an allocation-free alternative is - // even more verbose and also does not warrant pulling a - // hex string parser dependency. - let nonce_chunks = nonce.split_terminator('-') - .map(|dword| u32::from_str_radix(dword, 16).map_err(Into::into)) - .collect::<Result<Vec<_>>>(); - - let nonce = match nonce_chunks.ok().as_deref() { - Some(&[d0, d1, d2, d3, ..]) => { - let mut nonce = [0u8; 16]; - nonce[0..4].copy_from_slice(&d0.to_ne_bytes()); - nonce[4..8].copy_from_slice(&d1.to_ne_bytes()); - nonce[8..12].copy_from_slice(&d2.to_ne_bytes()); - nonce[12..16].copy_from_slice(&d3.to_ne_bytes()); - nonce - }, - _ => return Err(anyhow!("Couldn't parse Cygwin socket nonce: {}", nonce)), - }; - Ok(Rendezvous { port, nonce, uds_emulation: UdsEmulation::Cygwin }) - }, - _ => Err(anyhow!("Couldn't parse Cygwin socket: {}", buf)), - } - }, - // libassuan's own socket emulation - // Format: [<whitespace>?, port, .., '\n', <16 byte nonce>] - None => { - let pos = match contents.iter().position(|&x| x == b'\n') { - // Also ensure that there are exactly 16 bytes following - Some(pos) if pos + 1 + 16 == contents.len() => pos, - _ => return Err(anyhow!("Malformed socket description: {:?}", contents)), - }; - let port = std::str::from_utf8(&contents[..pos])?.trim().parse()?; - let mut nonce = [0u8; 16]; - nonce[..].copy_from_slice(&contents[pos + 1..]); - - Ok(Rendezvous { port, nonce, uds_emulation: UdsEmulation::Libassuan }) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn read_port_and_nonce() -> Result<()> { - let test_fn = super::read_port_and_nonce_from_string; - assert!(test_fn(b"\t 12 \n1234567890123456").is_ok()); - assert!(test_fn(b"\t 12 \n123456789012345").is_err()); - assert!(test_fn(b"\t 12 \n12345678901234567").is_err()); - - assert!(matches!( - test_fn(b" 12345\n\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"), - Ok(Rendezvous { - port: 12345, - uds_emulation: UdsEmulation::Libassuan, - nonce: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], - }) - )); - assert!(matches!( - test_fn(b" -152\n\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"), - Err(..) - )); - - assert!(matches!( - test_fn(b"!<socket >12345 s AABBCCDD-DDCCBBAA-01234567-890ABCDE\x00"), - Ok(Rendezvous { - port: 12345, - uds_emulation: UdsEmulation::Cygwin, - nonce: [ - 0xDD, 0xCC, 0xBB, 0xAA, - 0xAA, 0xBB, 0xCC, 0xDD, - 0x67, 0x45, 0x23, 0x01, - 0xDE, 0xBC, 0x0A, 0x89, - ] - }) - )); - assert!(matches!( - test_fn(b"!<socket >12345 s AABBCCDD-DDCCBBAA-01234567-890ABCDE"), - Ok(Rendezvous { - port: 12345, - uds_emulation: UdsEmulation::Cygwin, - nonce: [ - 0xDD, 0xCC, 0xBB, 0xAA, - 0xAA, 0xBB, 0xCC, 0xDD, - 0x67, 0x45, 0x23, 0x01, - 0xDE, 0xBC, 0x0A, 0x89, - ] - }) - )); - - Ok(()) - } -} diff --git a/ipc/src/assuan/socket/windows.rs b/ipc/src/assuan/socket/windows.rs new file mode 100644 index 00000000..02841e09 --- /dev/null +++ b/ipc/src/assuan/socket/windows.rs @@ -0,0 +1,167 @@ +//! Unix Domain Socket emulation for Windows. + +use std::io::Read; +use std::path::Path; +use std::fs::File; + +use anyhow::anyhow; + +use crate::Result; + +/// Socket connection data. +#[derive(Debug)] +pub struct Rendezvous { + pub port: u16, + pub uds_emulation: UdsEmulation, + pub nonce: [u8; 16], +} + +/// Unix domain socket emulation type (Windows only). +/// +/// Until Windows 10 Update 1803, Windows did not support native UNIX domain +/// sockets. To work around that, developers historically used TCP (readily +/// available) connection coupled with an authentication nonce (or "cookie"). +#[derive(Debug)] +pub enum UdsEmulation { + /// Cygwin socket emulation. + /// + /// File format: `!<socket >%u %c %08x-%08x-%08x-%08x` (scanf style) + /// %u: local TCP port + /// %c: socket type ("s" for `SOCK_STREAM`, "d" for `SOCK_DGRAM`) + /// %08x-%08x-%08x-%08x: authentication nonce + /// + /// Starting with client, both sides first exchange the 16-byte authentication + /// nonce, after which they exchange `ucred` structure (socket.h). + Cygwin, + /// Libassuan's custom socket emulation. + /// + /// File format: `<PORT>\n<NONCE>` + /// PORT: textual local TCP port (e.g. "12345") + /// NONCE: raw 16-byte authentication nonce + /// + /// After connecting, client has to authenticate itself by sending the + /// 16-byte authentication nonce. + Libassuan, +} + +/// Reads socket connection info from a Windows file emulating a Unix socket. +/// +/// Inspired by `read_port_and nonce` from assuan-socket.c. +pub fn read_port_and_nonce(fname: &Path) -> Result<Rendezvous> { + let mut file = File::open(fname)?; + // Socket connection info will be in either a <= 54 byte long Cygwin format + // or ~5+1+16 (modulo whitespace separators) custom libassuan format + let mut contents = Vec::with_capacity(64); + file.read_to_end(&mut contents)?; + + read_port_and_nonce_from_string(&contents) +} + +fn read_port_and_nonce_from_string(contents: &[u8]) -> Result<Rendezvous> { + let maybe_utf8 = std::str::from_utf8(contents).ok(); + + match maybe_utf8.and_then(|buf| buf.strip_prefix("!<socket >")) { + // libassuan's Cygwin compatible socket emulation. + // Format: "!<socket >%u %c %08x-%08x-%08x-%08x\x00" (scanf-like) + Some(buf) => { + let opt_skip_nul = buf.strip_suffix('\x00').unwrap_or(buf); + // Split into parts: port, kind of socket and nonce + let mut iter = opt_skip_nul.split_terminator(' '); + match (iter.next(), iter.next(), iter.next()) { + (Some(port), Some("s"), Some(nonce)) => { + let port = port.parse()?; + + // This is wasteful but an allocation-free alternative is + // even more verbose and also does not warrant pulling a + // hex string parser dependency. + let nonce_chunks = nonce.split_terminator('-') + .map(|dword| u32::from_str_radix(dword, 16).map_err(Into::into)) + .collect::<Result<Vec<_>>>(); + + let nonce = match nonce_chunks.ok().as_deref() { + Some(&[d0, d1, d2, d3, ..]) => { + let mut nonce = [0u8; 16]; + nonce[0..4].copy_from_slice(&d0.to_ne_bytes()); + nonce[4..8].copy_from_slice(&d1.to_ne_bytes()); + nonce[8..12].copy_from_slice(&d2.to_ne_bytes()); + nonce[12..16].copy_from_slice(&d3.to_ne_bytes()); + nonce + }, + _ => return Err(anyhow!("Couldn't parse Cygwin socket nonce: {}", nonce)), + }; + Ok(Rendezvous { port, nonce, uds_emulation: UdsEmulation::Cygwin }) + }, + _ => Err(anyhow!("Couldn't parse Cygwin socket: {}", buf)), + } + }, + // libassuan's own socket emulation + // Format: [<whitespace>?, port, .., '\n', <16 byte nonce>] + None => { + let pos = match contents.iter().position(|&x| x == b'\n') { + // Also ensure that there are exactly 16 bytes following + Some(pos) if pos + 1 + 16 == contents.len() => pos, + _ => return Err(anyhow!("Malformed socket description: {:?}", contents)), + }; + let port = std::str::from_utf8(&contents[..pos])?.trim().parse()?; + let mut nonce = [0u8; 16]; + nonce[..].copy_from_slice(&contents[pos + 1..]); + + Ok(Rendezvous { port, nonce, uds_emulation: UdsEmulation::Libassuan }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn read_port_and_nonce() -> Result<()> { + let test_fn = super::read_port_and_nonce_from_string; + assert!(test_fn(b"\t 12 \n1234567890123456").is_ok()); + assert!(test_fn(b"\t 12 \n123456789012345").is_err()); + assert!(test_fn(b"\t 12 \n12345678901234567").is_err()); + + assert!(matches!( + test_fn(b" 12345\n\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"), + Ok(Rendezvous { + port: 12345, + uds_emulation: UdsEmulation::Libassuan, + nonce: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + }) + )); + assert!(matches!( + test_fn(b" -152\n\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"), + Err(..) + )); + + assert!(matches!( + test_fn(b"!<socket >12345 s AABBCCDD-DDCCBBAA-01234567-890ABCDE\x00"), + Ok(Rendezvous { + port: 12345, + uds_emulation: UdsEmulation::Cygwin, + nonce: [ + 0xDD, 0xCC, 0xBB, 0xAA, + 0xAA, 0xBB, 0xCC, 0xDD, + 0x67, 0x45, 0x23, 0x01, + 0xDE, 0xBC, 0x0A, 0x89, + ] + }) + )); + assert!(matches!( + test_fn(b"!<socket >12345 s AABBCCDD-DDCCBBAA-01234567-890ABCDE"), + Ok(Rendezvous { + port: 12345, + uds_emulation: UdsEmulation::Cygwin, + nonce: [ + 0xDD, 0xCC, 0xBB, 0xAA, + 0xAA, 0xBB, 0xCC, 0xDD, + 0x67, 0x45, 0x23, 0x01, + 0xDE, 0xBC, 0x0A, 0x89, + ] + }) + )); + + Ok(()) + } +} |