use std::path::PathBuf;
use tokio::net::{UnixListener, UnixStream};
use crate::error::ServiceError;
pub type TransportListener = UnixListener;
pub type TransportStream = UnixStream;
pub type TransportReadHalf = tokio::io::ReadHalf<TransportStream>;
pub type TransportWriteHalf = tokio::io::WriteHalf<TransportStream>;
pub fn bind(path: PathBuf) -> Result<TransportListener, ServiceError> {
match std::fs::symlink_metadata(&path) {
Ok(metadata) => {
if metadata.file_type().is_symlink() {
return Err(ServiceError::Ipc(
"refusing to bind: socket path is a symlink".into(),
));
}
std::fs::remove_file(&path)
.map_err(|e| ServiceError::Ipc(format!("remove stale socket: {e}")))?;
}
Err(_) => {
}
}
UnixListener::bind(&path).map_err(|e| ServiceError::Ipc(format!("bind failed: {e}")))
}
pub async fn connect(path: &PathBuf) -> Result<TransportStream, ServiceError> {
UnixStream::connect(path)
.await
.map_err(|e| ServiceError::Ipc(format!("connect failed: {e}")))
}
pub fn peer_cred_check(stream: &TransportStream) -> bool {
#[cfg(target_os = "linux")]
{
use std::os::unix::io::AsRawFd;
let peer_uid = unsafe {
let mut creds: libc::ucred = std::mem::zeroed();
let mut len = std::mem::size_of::<libc::ucred>() as libc::socklen_t;
let ret = libc::getsockopt(
stream.as_raw_fd(),
libc::SOL_SOCKET,
libc::SO_PEERCRED,
&mut creds as *mut _ as *mut _,
&mut len,
);
if ret == -1 {
log::warn!("IPC: failed to get peer credentials, rejecting connection");
return false;
}
creds.uid
};
let my_uid = unsafe { libc::getuid() };
if peer_uid != my_uid {
log::warn!("IPC connection rejected: uid mismatch ({peer_uid} != {my_uid})");
return false;
}
}
#[cfg(target_os = "macos")]
{
use std::os::unix::io::AsRawFd;
let mut peer_uid: libc::uid_t = 0;
let mut peer_gid: libc::gid_t = 0;
if unsafe { libc::getpeereid(stream.as_raw_fd(), &mut peer_uid, &mut peer_gid) } != 0 {
log::warn!("IPC: failed to get peer credentials via getpeereid, rejecting connection");
return false;
}
let my_uid = unsafe { libc::getuid() };
if peer_uid != my_uid {
log::warn!("IPC connection rejected: uid mismatch ({peer_uid} != {my_uid})");
return false;
}
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
log::warn!("IPC: no peer credential check available on this platform");
}
true
}
pub fn cleanup(path: &PathBuf) {
let _ = std::fs::remove_file(path);
}
pub async fn accept(listener: &mut TransportListener) -> Result<TransportStream, std::io::Error> {
let (stream, _addr) = listener.accept().await?;
Ok(stream)
}
pub fn split(stream: TransportStream) -> (TransportReadHalf, TransportWriteHalf) {
tokio::io::split(stream)
}