diff --git a/apps/desktop/desktop_native/core/src/ipc/server.rs b/apps/desktop/desktop_native/core/src/ipc/server.rs index 2762a832ac6..dd7ab0c4804 100644 --- a/apps/desktop/desktop_native/core/src/ipc/server.rs +++ b/apps/desktop/desktop_native/core/src/ipc/server.rs @@ -6,7 +6,12 @@ use std::{ use futures::{SinkExt, StreamExt, TryFutureExt}; use anyhow::Result; +// Non-Unix uses interprocess local sockets +#[cfg(not(unix))] use interprocess::local_socket::{tokio::prelude::*, GenericFilePath, ListenerOptions}; +// Unix uses tokio's UnixListener to access peer credentials +#[cfg(unix)] +use tokio::net::UnixListener; use tokio::{ io::{AsyncRead, AsyncWrite}, sync::{broadcast, mpsc}, @@ -54,9 +59,15 @@ impl Server { let _ = std::fs::remove_file(path); } - let name = path.as_os_str().to_fs_name::()?; - let opts = ListenerOptions::new().name(name); - let listener = opts.create_tokio()?; + #[cfg(unix)] + let listener = UnixListener::bind(path)?; + + #[cfg(not(unix))] + let listener = { + let name = path.as_os_str().to_fs_name::()?; + let opts = ListenerOptions::new().name(name); + opts.create_tokio()? + }; // This broadcast channel is used for sending messages to all connected clients, and so the sender // will be stored in the server while the receiver will be cloned and passed to each client handler. @@ -74,7 +85,15 @@ impl Server { cancel_token: cancel_token.clone(), server_to_clients_send, }; - tokio::spawn(listen_incoming( + #[cfg(unix)] + tokio::spawn(listen_incoming_unix( + listener, + client_to_server_send, + server_to_clients_recv, + cancel_token, + )); + #[cfg(not(unix))] + tokio::spawn(listen_incoming_non_unix( listener, client_to_server_send, server_to_clients_recv, @@ -108,8 +127,78 @@ impl Drop for Server { } } -async fn listen_incoming( - listener: LocalSocketListener, +#[cfg(unix)] +async fn listen_incoming_unix( + listener: UnixListener, + client_to_server_send: mpsc::Sender, + server_to_clients_recv: broadcast::Receiver, + cancel_token: CancellationToken, +) { + // We use a simple incrementing ID for each client + let mut next_client_id = 1_u32; + + loop { + use crate::ssh_agent::peerinfo::gather::get_peer_info; + + tokio::select! { + _ = cancel_token.cancelled() => { + info!("IPC server cancelled."); + break; + }, + + // A new client connection has been established + msg = listener.accept() => { + match msg { + Ok((client_stream, _addr)) => { + let client_id = next_client_id; + next_client_id += 1; + + // Try to log peer credentials + match client_stream.peer_cred() { + Ok(peer) => { + if let Some(pid) = peer.pid() { + let peer_info = match get_peer_info(pid as u32) { + Ok(info) => info, + Err(_) => crate::ssh_agent::peerinfo::models::PeerInfo::unknown(), + }; + info!(client_id, pid, uid = peer.uid(), gid = peer.gid(), peer_info = ?peer_info, "IPC client connected (peer credentials)"); + } else { + info!(client_id, uid = peer.uid(), gid = peer.gid(), "IPC client connected (peer credentials, no pid)"); + } + }, + Err(e) => { + error!(client_id, error = %e, "Failed to get peer credentials"); + } + } + + let future = handle_connection( + client_stream, + client_to_server_send.clone(), + // We resubscribe to the receiver here so this task can have it's own copy + // Note that this copy will only receive messages sent after this point, + // but that is okay, realistically we don't want any messages before we get a chance + // to send the connected message to the client, which is done inside [`handle_connection`] + server_to_clients_recv.resubscribe(), + cancel_token.clone(), + client_id + ); + tokio::spawn(future.map_err(|e| { + error!(error = %e, "Error handling connection") + })); + }, + Err(e) => { + error!(error = %e, "Error accepting connection"); + break; + }, + } + } + } + } +} + +#[cfg(not(unix))] +async fn listen_incoming_non_unix( + listener: interprocess::local_socket::LocalSocketListener, client_to_server_send: mpsc::Sender, server_to_clients_recv: broadcast::Receiver, cancel_token: CancellationToken, diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs index 699203d613d..5ab7eb8f4fa 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/gather.rs @@ -1,3 +1,5 @@ +use sha2::{Digest, Sha256}; +use std::{fs::File, io::Read, path::Path}; use sysinfo::{Pid, System}; use super::models::PeerInfo; @@ -12,12 +14,35 @@ pub fn get_peer_info(peer_pid: u32) -> Result { } }; + let exe_hash = process.exe().and_then(|p| hash_file_if_exists(p)); + return Ok(PeerInfo::new( peer_pid, process.pid().as_u32(), peer_process_name, + exe_hash, )); } Err("Failed to get process".to_string()) } + +fn hash_file_if_exists(path: &Path) -> Option { + if path.as_os_str().is_empty() || !path.exists() { + return None; + } + let mut f = File::open(path).ok()?; + let mut hasher = Sha256::new(); + let mut buf = [0u8; 64 * 1024]; + loop { + let n = match f.read(&mut buf) { + Ok(n) => n, + Err(_) => return None, + }; + if n == 0 { + break; + } + hasher.update(&buf[..n]); + } + Some(format!("{:x}", hasher.finalize())) +} diff --git a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs index fad535cb80e..f46a18fa2e9 100644 --- a/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs +++ b/apps/desktop/desktop_native/core/src/ssh_agent/peerinfo/models.rs @@ -9,16 +9,18 @@ pub struct PeerInfo { uid: u32, pid: u32, process_name: String, + exe_hash: Option, is_forwarding: Arc, host_key: Arc>>, } impl PeerInfo { - pub fn new(uid: u32, pid: u32, process_name: String) -> Self { + pub fn new(uid: u32, pid: u32, process_name: String, exe_hash: Option) -> Self { Self { uid, pid, process_name, + exe_hash, is_forwarding: Arc::new(AtomicBool::new(false)), host_key: Arc::new(Mutex::new(Vec::new())), } @@ -29,6 +31,7 @@ impl PeerInfo { uid: 0, pid: 0, process_name: "Unknown application".to_string(), + exe_hash: None, is_forwarding: Arc::new(AtomicBool::new(false)), host_key: Arc::new(Mutex::new(Vec::new())), } @@ -46,6 +49,10 @@ impl PeerInfo { &self.process_name } + pub fn exe_hash(&self) -> Option<&str> { + self.exe_hash.as_deref() + } + pub fn is_forwarding(&self) -> bool { self.is_forwarding .load(std::sync::atomic::Ordering::Relaxed)