sequoia_gpg_agent/assuan/socket.rs
1//! Select functionality from [assuan-socket.c].
2//!
3//! [assuan-socket.c]: https://github.com/gpg/libassuan/blob/master/src/assuan-socket.c
4
5use std::path::Path;
6
7use crate::Result;
8
9#[cfg(windows)]
10mod windows;
11
12#[cfg(windows)]
13pub(crate) type IpcStream = tokio::net::TcpStream;
14#[cfg(unix)]
15pub(crate) type IpcStream = tokio::net::UnixStream;
16
17/// Connects to a local socket, returning a Tokio-enabled async connection.
18///
19/// Supports regular local domain sockets under Unix-like systems and
20/// either Cygwin or libassuan's socket emulation on Windows.
21///
22/// # Panic
23///
24/// This function panics if not called from within a Tokio runtime.
25pub(crate) fn sock_connect(path: impl AsRef<Path>) -> Result<IpcStream> {
26 platform! {
27 unix => {
28 let stream = std::os::unix::net::UnixStream::connect(path)?;
29 stream.set_nonblocking(true)?;
30 Ok(tokio::net::UnixStream::from_std(stream)?)
31 },
32 windows => {
33 use std::io::{Write, Read};
34 use std::net::{Ipv4Addr, TcpStream};
35
36 use windows::{
37 read_port_and_nonce,
38 Rendezvous,
39 UdsEmulation,
40 };
41
42 let rendezvous = read_port_and_nonce(path.as_ref())?;
43 let Rendezvous { port, uds_emulation, nonce } = rendezvous;
44
45 let mut stream = TcpStream::connect((Ipv4Addr::LOCALHOST, port))?;
46 stream.set_nodelay(true)?;
47
48 // Authorize ourselves with nonce read from the file
49 stream.write(&nonce)?;
50
51 if let UdsEmulation::Cygwin = uds_emulation {
52 // The client sends the nonce back - not useful. Do a dummy read
53 stream.read_exact(&mut [0u8; 16])?;
54
55 // Send our credentials as expected by libassuan:
56 // [ pid |uid|gid] (8 bytes)
57 // [_|_|_|_|_|_|_|_]
58 let mut creds = [0u8; 8]; // uid = gid = 0
59 creds[..4].copy_from_slice(&std::process::id().to_ne_bytes());
60 stream.write_all(&creds)?;
61 // FIXME: libassuan in theory reads only 8 bytes here, but
62 // somehow 12 have to be written for the server to progress (tested
63 // on mingw-x86_64-gnupg).
64 // My bet is that mingw socket API does that transparently instead
65 // and expects to read an actual `ucred` struct (socket.h) which is
66 // three `__u32`s.
67 // Here, neither client nor server uses it, so just send dummy bytes
68 stream.write_all(&[0u8; 4])?;
69
70 // Receive back credentials. We don't need them.
71 stream.read_exact(&mut [0u8; 12])?;
72 }
73
74 stream.set_nonblocking(true)?;
75 Ok(tokio::net::TcpStream::from_std(stream)?)
76 },
77 }
78}