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}