use std::path::{Path, PathBuf};
pub type ControlId = PathBuf;
pub type ClientStream = tokio::net::UnixStream;
pub type ServerStream = tokio::net::UnixStream;
pub fn control_id(base: &Path) -> ControlId {
base.join("control.sock")
}
pub fn control_id_from_str(s: &str) -> ControlId {
PathBuf::from(s)
}
pub fn is_daemon_running(id: &Path) -> bool {
std::os::unix::net::UnixStream::connect(id).is_ok()
}
#[derive(Debug)]
pub struct ControlListener(tokio::net::UnixListener);
impl ControlListener {
pub async fn accept(&mut self) -> std::io::Result<Option<ServerStream>> {
self.accept_with(leviath_sys::peer_uid).await
}
async fn accept_with(
&mut self,
peer_uid: fn(&ServerStream) -> Option<u32>,
) -> std::io::Result<Option<ServerStream>> {
self.0.accept().await.map(|(stream, _addr)| {
peer_is_ours(peer_uid(&stream), leviath_sys::current_uid()).then_some(stream)
})
}
}
pub fn bind_control_listener(id: &Path) -> std::io::Result<ControlListener> {
if id.exists() {
if std::os::unix::net::UnixStream::connect(id).is_ok() {
return Err(std::io::Error::new(
std::io::ErrorKind::AddrInUse,
"a leviath daemon is already running on this control socket",
));
}
let _ = std::fs::remove_file(id);
}
let parent = id.parent().expect("control socket path has a parent");
std::fs::create_dir_all(parent)?;
let _ = leviath_sys::secure_dir_perms(parent);
let listener = tokio::net::UnixListener::bind(id)?;
let _ = leviath_sys::secure_file_perms(id);
Ok(ControlListener(listener))
}
fn peer_is_ours(peer: Option<u32>, ours: u32) -> bool {
match peer {
Some(uid) if uid == ours => true,
Some(uid) => {
tracing::warn!(
peer_uid = uid,
daemon_uid = ours,
"refused a control connection from another user"
);
false
}
None => {
tracing::warn!("refused a control connection whose peer uid could not be determined");
false
}
}
}
pub async fn connect(id: &Path) -> std::io::Result<ClientStream> {
tokio::net::UnixStream::connect(id).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn control_id_is_under_base() {
assert_eq!(
control_id(Path::new("/x/.leviath")),
Path::new("/x/.leviath/control.sock")
);
}
#[test]
fn control_id_from_str_is_the_path() {
assert_eq!(
control_id_from_str("/tmp/my.sock"),
PathBuf::from("/tmp/my.sock")
);
}
#[tokio::test]
async fn bind_locks_down_the_socket_and_its_directory() {
use std::os::unix::fs::PermissionsExt;
let dir = tempfile::tempdir().unwrap();
let id = dir.path().join("nested").join("control.sock");
let _listener = bind_control_listener(&id).unwrap();
let mode = |p: &Path| std::fs::metadata(p).unwrap().permissions().mode() & 0o777;
assert_eq!(mode(&id), 0o600, "socket must be owner-only");
assert_eq!(
mode(id.parent().unwrap()),
0o700,
"socket directory must be owner-only"
);
}
#[tokio::test]
async fn accept_skips_connections_that_are_not_ours() {
fn foreign_uid(_: &ServerStream) -> Option<u32> {
Some(leviath_sys::current_uid().wrapping_add(1))
}
fn unknown_uid(_: &ServerStream) -> Option<u32> {
None
}
for (label, lookup) in [
(
"another user",
foreign_uid as fn(&ServerStream) -> Option<u32>,
),
("an undeterminable uid", unknown_uid),
] {
let dir = tempfile::tempdir().unwrap();
let id = dir.path().join("control.sock");
let mut listener = bind_control_listener(&id).unwrap();
let _client = connect(&id).await.expect("connecting succeeds");
let accepted = listener
.accept_with(lookup)
.await
.expect("accepting itself succeeds");
assert!(
accepted.is_none(),
"a connection from {label} must not be handed to the daemon"
);
}
}
#[test]
fn peer_is_ours_admits_only_this_user() {
assert!(peer_is_ours(Some(1000), 1000));
assert!(!peer_is_ours(Some(1001), 1000));
assert!(!peer_is_ours(None, 1000), "an unknown peer fails closed");
}
#[tokio::test]
async fn accept_admits_a_connection_from_the_same_user() {
let dir = tempfile::tempdir().unwrap();
let id = dir.path().join("control.sock");
let mut listener = bind_control_listener(&id).unwrap();
let client = tokio::spawn(async move { connect(&id).await });
let accepted = listener.accept().await.expect("accept succeeds");
assert!(accepted.is_some(), "same-uid peer must be admitted");
client.await.unwrap().unwrap();
}
#[tokio::test]
async fn bind_removes_a_stale_socket_file() {
let dir = tempfile::tempdir().unwrap();
let id = control_id(dir.path());
std::fs::write(&id, b"stale").unwrap();
let listener = bind_control_listener(&id).unwrap();
assert!(id.exists());
drop(listener);
}
#[tokio::test]
async fn bind_errors_when_parent_cannot_be_created() {
let dir = tempfile::tempdir().unwrap();
let blocker = dir.path().join("blocker");
std::fs::write(&blocker, b"x").unwrap();
let id = blocker.join("control.sock"); assert!(bind_control_listener(&id).is_err());
}
#[tokio::test]
async fn bind_errors_when_target_path_is_a_directory() {
let dir = tempfile::tempdir().unwrap();
let id = dir.path().join("control.sock");
std::fs::create_dir(&id).unwrap(); assert!(bind_control_listener(&id).is_err());
}
}