use std::io;
use std::path::Path;
use super::{bind_socket_file, group_gid, set_socket_group, OperatorListener, OPERATOR_GROUP};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PeerClass {
Service,
Operator,
Reader,
}
pub fn classify_peer(
peer_uid: u32,
peer_gids: &[u32],
server_uid: u32,
operator_gid: Option<u32>,
reader_gid: Option<u32>,
) -> Option<PeerClass> {
if peer_uid == 0 || peer_uid == server_uid {
return Some(PeerClass::Service);
}
let member = |gid: Option<u32>| gid.is_some_and(|g| peer_gids.contains(&g));
if member(operator_gid) {
Some(PeerClass::Operator)
} else if member(reader_gid) {
Some(PeerClass::Reader)
} else {
None
}
}
pub fn bind_reader_socket(
path: impl AsRef<Path>,
mode: u32,
reader_group: &str,
) -> io::Result<OperatorListener> {
let path = path.as_ref();
let inner = bind_socket_file(path, mode)?;
let reader_gid = group_gid(reader_group);
set_socket_group(path, reader_group, reader_gid);
Ok(OperatorListener {
inner,
path: path.to_path_buf(),
server_uid: nix::unistd::geteuid().as_raw(),
operator_gid: group_gid(OPERATOR_GROUP),
reader_gid,
})
}
#[cfg(test)]
mod tests {
use super::*;
const SERVER: u32 = 900;
const OPERATOR: u32 = 988;
const SENSORS: u32 = 977;
const PLUGINS: u32 = 966;
fn class(uid: u32, gids: &[u32]) -> Option<PeerClass> {
classify_peer(uid, gids, SERVER, Some(OPERATOR), Some(SENSORS))
}
#[test]
fn service_peers_are_root_and_the_server_uid() {
assert_eq!(class(0, &[0]), Some(PeerClass::Service));
assert_eq!(class(SERVER, &[SERVER]), Some(PeerClass::Service));
}
#[test]
fn the_operator_group_outranks_the_reader_group() {
assert_eq!(class(1000, &[1000, OPERATOR]), Some(PeerClass::Operator));
assert_eq!(
class(1000, &[1000, SENSORS, OPERATOR]),
Some(PeerClass::Operator)
);
}
#[test]
fn a_reader_group_member_is_only_a_reader() {
assert_eq!(class(61_001, &[PLUGINS, SENSORS]), Some(PeerClass::Reader));
}
#[test]
fn anyone_else_is_refused() {
assert_eq!(class(61_001, &[PLUGINS]), None);
assert_eq!(class(1000, &[1000]), None);
assert_eq!(
classify_peer(61_001, &[SENSORS], SERVER, Some(OPERATOR), None),
None
);
assert_eq!(classify_peer(1000, &[OPERATOR], SERVER, None, None), None);
}
#[tokio::test]
async fn a_reader_socket_classifies_its_own_uid_as_service() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("bus.sock");
let listener = bind_reader_socket(&path, 0o660, "ados-test-no-such-group").unwrap();
let _client = tokio::net::UnixStream::connect(&path).await.unwrap();
let (_stream, class) = listener.accept_classified().await.unwrap();
assert_eq!(class, PeerClass::Service);
}
}