1use std::io;
20use std::path::Path;
21
22pub use imp::*;
23
24#[cfg(unix)]
25mod imp {
26 use super::*;
27 use tokio::net::{UnixListener, UnixStream};
28
29 pub type ServerStream = UnixStream;
31 pub type ServerReadHalf = tokio::net::unix::OwnedReadHalf;
32 pub type ServerWriteHalf = tokio::net::unix::OwnedWriteHalf;
33 pub type ClientStream = UnixStream;
35 pub type ClientReadHalf = tokio::net::unix::OwnedReadHalf;
36 pub type ClientWriteHalf = tokio::net::unix::OwnedWriteHalf;
37
38 pub fn split_server(s: ServerStream) -> (ServerReadHalf, ServerWriteHalf) {
39 s.into_split()
40 }
41
42 pub fn split_client(s: ClientStream) -> (ClientReadHalf, ClientWriteHalf) {
43 s.into_split()
44 }
45
46 pub async fn connect(path: &Path) -> io::Result<ClientStream> {
47 UnixStream::connect(path).await
48 }
49
50 pub struct Listener(UnixListener);
52
53 impl Listener {
54 pub fn bind(path: &Path) -> io::Result<Self> {
57 UnixListener::bind(path).map(Self)
58 }
59
60 pub async fn accept(&mut self) -> io::Result<ServerStream> {
61 self.0.accept().await.map(|(s, _)| s)
62 }
63
64 pub fn as_unix(&self) -> &UnixListener {
65 &self.0
66 }
67 }
68
69 impl From<UnixListener> for Listener {
70 fn from(l: UnixListener) -> Self {
71 Self(l)
72 }
73 }
74}
75
76#[cfg(windows)]
77mod imp {
78 use super::*;
79 use std::pin::Pin;
80 use std::task::{Context, Poll};
81 use std::time::{Duration, Instant};
82
83 use tokio::io::{AsyncRead, ReadBuf};
84 use tokio::net::windows::named_pipe::{ClientOptions, NamedPipeClient, NamedPipeServer, ServerOptions};
85
86 pub type ServerStream = NamedPipeServer;
88 pub type ServerReadHalf = EofOnBrokenPipe<tokio::io::ReadHalf<NamedPipeServer>>;
89 pub type ServerWriteHalf = tokio::io::WriteHalf<NamedPipeServer>;
90 pub type ClientStream = NamedPipeClient;
92 pub type ClientReadHalf = EofOnBrokenPipe<tokio::io::ReadHalf<NamedPipeClient>>;
93 pub type ClientWriteHalf = tokio::io::WriteHalf<NamedPipeClient>;
94
95 pub fn split_server(s: ServerStream) -> (ServerReadHalf, ServerWriteHalf) {
96 let (r, w) = tokio::io::split(s);
97 (EofOnBrokenPipe(r), w)
98 }
99
100 pub fn split_client(s: ClientStream) -> (ClientReadHalf, ClientWriteHalf) {
101 let (r, w) = tokio::io::split(s);
102 (EofOnBrokenPipe(r), w)
103 }
104
105 pub struct EofOnBrokenPipe<R>(R);
109
110 impl<R: AsyncRead + Unpin> AsyncRead for EofOnBrokenPipe<R> {
111 fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll<io::Result<()>> {
112 match Pin::new(&mut self.0).poll_read(cx, buf) {
113 Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::BrokenPipe => Poll::Ready(Ok(())),
114 other => other,
115 }
116 }
117 }
118
119 const BUSY_RETRY: Duration = Duration::from_secs(2);
122
123 pub async fn connect(path: &Path) -> io::Result<ClientStream> {
127 use std::os::windows::io::AsRawHandle;
128 let name = pipe_name(path);
129 let deadline = Instant::now() + BUSY_RETRY;
130 let client = loop {
131 match ClientOptions::new().open(&name) {
132 Ok(c) => break c,
133 Err(e)
134 if e.raw_os_error() == Some(windows_sys::Win32::Foundation::ERROR_PIPE_BUSY as i32)
135 && Instant::now() < deadline =>
136 {
137 tokio::time::sleep(Duration::from_millis(5)).await;
138 }
139 Err(e) => return Err(e),
140 }
141 };
142 win::verify_server_is_current_user(client.as_raw_handle())?;
143 Ok(client)
144 }
145
146 pub struct Listener {
149 name: String,
150 security: win::PipeSecurity,
151 next: NamedPipeServer,
152 }
153
154 impl Listener {
155 pub fn bind(path: &Path) -> io::Result<Self> {
159 let name = pipe_name(path);
160 let security = win::PipeSecurity::current_user_only()?;
161 let next = security.create(ServerOptions::new().first_pipe_instance(true).reject_remote_clients(true), &name)?;
162 Ok(Self { name, security, next })
163 }
164
165 fn instance(&self) -> io::Result<NamedPipeServer> {
166 self.security.create(ServerOptions::new().reject_remote_clients(true), &self.name)
167 }
168
169 pub async fn accept(&mut self) -> io::Result<ServerStream> {
173 if let Err(e) = self.next.connect().await {
174 if let Ok(fresh) = self.instance() {
177 self.next = fresh;
178 }
179 return Err(e);
180 }
181 let fresh = self.instance()?;
184 Ok(std::mem::replace(&mut self.next, fresh))
185 }
186 }
187
188 pub(crate) fn create_owner_only_pipe(name: &str, first: bool) -> io::Result<NamedPipeServer> {
193 win::PipeSecurity::current_user_only()?
194 .create(ServerOptions::new().first_pipe_instance(first).reject_remote_clients(true), name)
195 }
196
197 pub(crate) fn verify_pipe_server(client: &NamedPipeClient) -> io::Result<()> {
199 use std::os::windows::io::AsRawHandle;
200 win::verify_server_is_current_user(client.as_raw_handle())
201 }
202
203 pub fn pipe_name(path: &Path) -> String {
214 use sha2::{Digest, Sha256};
215 let key = endpoint_key(path);
216 let digest = Sha256::digest(key.as_bytes());
217 let hex: String = digest[..16].iter().map(|b| format!("{b:02x}")).collect();
218 format!(r"\\.\pipe\synaps-daemon-{hex}")
219 }
220
221 fn endpoint_key(path: &Path) -> String {
222 let abs = std::path::absolute(path).unwrap_or_else(|_| path.to_path_buf());
223 let resolved = match (abs.parent(), abs.file_name()) {
224 (Some(dir), Some(name)) => dir.canonicalize().map(|d| d.join(name)).unwrap_or_else(|_| abs.clone()),
225 _ => abs,
226 };
227 let s = resolved.to_string_lossy().replace('/', "\\");
228 let s = if let Some(rest) = s.strip_prefix(r"\\?\UNC\") {
229 format!(r"\\{rest}")
230 } else if let Some(rest) = s.strip_prefix(r"\\?\") {
231 rest.to_string()
232 } else {
233 s
234 };
235 s.to_lowercase()
236 }
237
238 mod win {
239 use std::ffi::c_void;
240 use std::io;
241 use std::ptr::null_mut;
242
243 use tokio::net::windows::named_pipe::{NamedPipeServer, ServerOptions};
244 use windows_sys::Win32::Foundation::{CloseHandle, LocalFree, HANDLE};
245 use windows_sys::Win32::Security::Authorization::{
246 ConvertSidToStringSidW, ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
247 };
248 use windows_sys::Win32::Security::{
249 EqualSid, GetTokenInformation, TokenUser, PSECURITY_DESCRIPTOR, PSID, SECURITY_ATTRIBUTES, TOKEN_QUERY,
250 TOKEN_USER,
251 };
252 use windows_sys::Win32::System::Pipes::GetNamedPipeServerProcessId;
253 use windows_sys::Win32::System::Threading::{
254 GetCurrentProcess, OpenProcess, OpenProcessToken, PROCESS_QUERY_LIMITED_INFORMATION,
255 };
256
257 struct Handle(HANDLE);
258 impl Drop for Handle {
259 fn drop(&mut self) {
260 unsafe { CloseHandle(self.0) };
262 }
263 }
264
265 struct TokenUserBuf(Vec<u64>);
267
268 impl TokenUserBuf {
269 fn of(process: HANDLE) -> io::Result<Self> {
270 let mut token: HANDLE = null_mut();
271 if unsafe { OpenProcessToken(process, TOKEN_QUERY, &mut token) } == 0 {
273 return Err(io::Error::last_os_error());
274 }
275 let token = Handle(token);
276 let mut len = 0u32;
277 unsafe { GetTokenInformation(token.0, TokenUser, null_mut(), 0, &mut len) };
279 if len == 0 {
280 return Err(io::Error::last_os_error());
281 }
282 let mut buf = vec![0u64; (len as usize).div_ceil(8)];
283 if unsafe { GetTokenInformation(token.0, TokenUser, buf.as_mut_ptr().cast(), len, &mut len) } == 0 {
285 return Err(io::Error::last_os_error());
286 }
287 Ok(Self(buf))
288 }
289
290 fn current() -> io::Result<Self> {
291 Self::of(unsafe { GetCurrentProcess() })
293 }
294
295 fn sid(&self) -> PSID {
296 unsafe { (*(self.0.as_ptr() as *const TOKEN_USER)).User.Sid }
298 }
299 }
300
301 fn sid_string(sid: PSID) -> io::Result<String> {
302 let mut s: *mut u16 = null_mut();
303 if unsafe { ConvertSidToStringSidW(sid, &mut s) } == 0 {
305 return Err(io::Error::last_os_error());
306 }
307 let out = unsafe {
309 let len = (0..).take_while(|&i| *s.add(i) != 0).count();
310 String::from_utf16_lossy(std::slice::from_raw_parts(s, len))
311 };
312 unsafe { LocalFree(s.cast()) };
314 Ok(out)
315 }
316
317 pub struct PipeSecurity(PSECURITY_DESCRIPTOR);
321
322 unsafe impl Send for PipeSecurity {}
324 unsafe impl Sync for PipeSecurity {}
325
326 impl PipeSecurity {
327 pub fn current_user_only() -> io::Result<Self> {
328 let user = sid_string(TokenUserBuf::current()?.sid())?;
329 let sddl: Vec<u16> = format!("D:P(A;;GA;;;SY)(A;;GA;;;{user})").encode_utf16().chain([0]).collect();
330 let mut sd: PSECURITY_DESCRIPTOR = null_mut();
331 if unsafe { ConvertStringSecurityDescriptorToSecurityDescriptorW(sddl.as_ptr(), SDDL_REVISION_1, &mut sd, null_mut()) } == 0 {
333 return Err(io::Error::last_os_error());
334 }
335 Ok(Self(sd))
336 }
337
338 pub fn create(&self, opts: &ServerOptions, name: &str) -> io::Result<NamedPipeServer> {
339 let mut sa = SECURITY_ATTRIBUTES {
340 nLength: std::mem::size_of::<SECURITY_ATTRIBUTES>() as u32,
341 lpSecurityDescriptor: self.0,
342 bInheritHandle: 0,
343 };
344 unsafe { opts.create_with_security_attributes_raw(name, &mut sa as *mut SECURITY_ATTRIBUTES as *mut c_void) }
346 }
347 }
348
349 impl Drop for PipeSecurity {
350 fn drop(&mut self) {
351 unsafe { LocalFree(self.0) };
353 }
354 }
355
356 pub fn verify_server_is_current_user(pipe: std::os::windows::io::RawHandle) -> io::Result<()> {
358 let mut pid = 0u32;
359 if unsafe { GetNamedPipeServerProcessId(pipe as HANDLE, &mut pid) } == 0 {
361 return Err(io::Error::last_os_error());
362 }
363 let refuse = |why: String| {
364 io::Error::new(
365 io::ErrorKind::PermissionDenied,
366 format!("daemon pipe is served by pid {pid}, {why}; refusing to connect"),
367 )
368 };
369 let process = unsafe { OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) };
371 if process.is_null() {
372 return Err(refuse(format!("whose owner cannot be read ({})", io::Error::last_os_error())));
373 }
374 let process = Handle(process);
375 let theirs = TokenUserBuf::of(process.0).map_err(|e| refuse(format!("whose owner cannot be read ({e})")))?;
376 let ours = TokenUserBuf::current()?;
377 if unsafe { EqualSid(ours.sid(), theirs.sid()) } == 0 {
379 return Err(refuse("which runs as another user".into()));
380 }
381 Ok(())
382 }
383 }
384}
385
386#[cfg(all(test, windows))]
387mod tests {
388 use super::*;
389 use tokio::io::{AsyncReadExt, AsyncWriteExt};
390
391 fn endpoint() -> (tempfile::TempDir, std::path::PathBuf) {
392 let d = tempfile::tempdir().unwrap();
393 let p = d.path().join("daemon.sock");
394 (d, p)
395 }
396
397 #[test]
398 fn pipe_name_is_stable_and_case_and_separator_insensitive() {
399 let a = pipe_name(Path::new(r"C:\Users\Me\.synaps-cli\run\daemon.sock"));
400 let b = pipe_name(Path::new("c:/users/me/.synaps-cli/run/DAEMON.sock"));
401 assert_eq!(a, b);
402 assert!(a.starts_with(r"\\.\pipe\synaps-daemon-"));
403 assert_ne!(a, pipe_name(Path::new(r"C:\Users\Me\.synaps-cli\run\daemon-work.sock")));
404 }
405
406 #[test]
407 fn pipe_name_resolves_the_directory_spelling() {
408 let (d, p) = endpoint();
409 let canon = d.path().canonicalize().unwrap().join("daemon.sock");
410 assert!(canon.to_string_lossy().starts_with(r"\\?\"));
411 assert_eq!(pipe_name(&p), pipe_name(&canon));
412 let dotted = d.path().join("sub").join("..").join("daemon.sock");
413 assert_eq!(pipe_name(&p), pipe_name(&dotted));
414 }
415
416 #[tokio::test]
417 async fn round_trip_and_peer_close_reads_as_eof() {
418 let (_d, path) = endpoint();
419 let mut l = Listener::bind(&path).unwrap();
420 let srv = tokio::spawn(async move {
421 let s = l.accept().await.unwrap();
422 let (mut r, mut w) = split_server(s);
423 let mut buf = [0u8; 5];
424 r.read_exact(&mut buf).await.unwrap();
425 w.write_all(&buf).await.unwrap();
426 let mut rest = Vec::new();
428 r.read_to_end(&mut rest).await.unwrap();
429 assert!(rest.is_empty());
430 });
431 let c = connect(&path).await.unwrap();
432 let (mut r, mut w) = split_client(c);
433 w.write_all(b"hello").await.unwrap();
434 let mut buf = [0u8; 5];
435 r.read_exact(&mut buf).await.unwrap();
436 assert_eq!(&buf, b"hello");
437 drop((r, w));
438 srv.await.unwrap();
439 }
440
441 #[tokio::test]
442 async fn serves_several_clients_and_refuses_a_second_server() {
443 let (_d, path) = endpoint();
444 let mut l = Listener::bind(&path).unwrap();
445 assert!(Listener::bind(&path).is_err(), "a served name is never shared");
446 let srv = tokio::spawn(async move {
447 for _ in 0..3 {
448 let s = l.accept().await.unwrap();
449 let (_r, mut w) = split_server(s);
450 w.write_all(b"x").await.unwrap();
451 }
452 });
453 for _ in 0..3 {
454 let (mut r, _w) = split_client(connect(&path).await.unwrap());
455 let mut b = [0u8; 1];
456 r.read_exact(&mut b).await.unwrap();
457 }
458 srv.await.unwrap();
459 }
460
461 #[tokio::test]
462 async fn connect_without_a_server_is_not_found() {
463 let (_d, path) = endpoint();
464 let e = connect(&path).await.unwrap_err();
465 assert_eq!(e.kind(), io::ErrorKind::NotFound);
466 }
467}