use crate::platform;
use std::{
fs::{self, File},
io,
os::unix::fs::{FileTypeExt, MetadataExt},
path::{Path, PathBuf},
};
use tokio::net::{UnixListener, UnixStream};
pub(crate) type Stream = UnixStream;
pub(crate) struct Listener {
listener: UnixListener,
}
pub(crate) fn user() -> io::Result<String> {
Ok(super::current_user().to_string())
}
pub(crate) fn owned(file: &File) -> io::Result<bool> {
Ok(file.metadata()?.uid().to_string() == user()?)
}
pub(crate) fn validate_directory(path: &Path) -> io::Result<()> {
let metadata = fs::symlink_metadata(path)?;
if !metadata.is_dir()
|| metadata.uid().to_string() != user()?
|| metadata.mode() & super::GROUP_OTHER_BITS != 0
{
return Err(io::ErrorKind::PermissionDenied.into());
}
Ok(())
}
const STICKY_BIT: u32 = 0o1000;
pub(crate) fn directory(namespace: &str, user: &str) -> io::Result<PathBuf> {
#[cfg(target_os = "macos")]
let root = Path::new("/private/tmp");
#[cfg(not(target_os = "macos"))]
let root = Path::new("/tmp");
for parent in root.ancestors().skip(1) {
let metadata = fs::symlink_metadata(parent)?;
if !metadata.is_dir()
|| metadata.uid() != 0
|| metadata.mode() & super::GROUP_OTHER_WRITE_BITS != 0
{
return Err(io::ErrorKind::PermissionDenied.into());
}
}
let metadata = fs::symlink_metadata(root)?;
if !metadata.is_dir() || metadata.uid() != 0 || metadata.mode() & STICKY_BIT == 0 {
return Err(io::ErrorKind::PermissionDenied.into());
}
let directory = root.join(format!("artifactize-ipc-{user}-{}", namespace));
match platform::create_private_dir(&directory) {
Ok(()) => {}
Err(error) if error.kind() == io::ErrorKind::AlreadyExists => {}
Err(error) => return Err(error),
}
validate_directory(&directory)?;
Ok(directory)
}
pub(crate) fn address(directory: &Path, _identity: &str) -> PathBuf {
directory.join("hub.sock")
}
fn validate_socket(address: &Path) -> io::Result<()> {
let metadata = fs::symlink_metadata(address)?;
if !metadata.file_type().is_socket() || metadata.uid().to_string() != user()? {
return Err(io::ErrorKind::PermissionDenied.into());
}
Ok(())
}
pub(crate) fn listen(address: &Path) -> io::Result<Listener> {
match validate_socket(address) {
Ok(()) => fs::remove_file(address)?,
Err(error) if error.kind() == io::ErrorKind::NotFound => {}
Err(error) => return Err(error),
}
let listener = UnixListener::bind(address)?;
Ok(Listener { listener })
}
pub(crate) async fn connect(address: &Path) -> io::Result<Stream> {
validate_socket(address)?;
let stream = UnixStream::connect(address).await?;
if stream.peer_cred()?.uid().to_string() != user()? {
return Err(io::ErrorKind::PermissionDenied.into());
}
Ok(stream)
}
impl Listener {
pub async fn accept(&mut self) -> io::Result<Stream> {
let (stream, _) = self.listener.accept().await?;
Ok(stream)
}
}
pub(crate) fn validate_peer(stream: &Stream) -> io::Result<()> {
if stream.peer_cred()?.uid().to_string() != user()? {
return Err(io::ErrorKind::PermissionDenied.into());
}
Ok(())
}