ados_protocol/ipc/
peer_class.rs1use std::io;
5use std::path::Path;
6
7use super::{bind_socket_file, group_gid, set_socket_group, OperatorListener, OPERATOR_GROUP};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum PeerClass {
12 Service,
14 Operator,
16 Reader,
19}
20
21pub fn classify_peer(
28 peer_uid: u32,
29 peer_gids: &[u32],
30 server_uid: u32,
31 operator_gid: Option<u32>,
32 reader_gid: Option<u32>,
33) -> Option<PeerClass> {
34 if peer_uid == 0 || peer_uid == server_uid {
35 return Some(PeerClass::Service);
36 }
37 let member = |gid: Option<u32>| gid.is_some_and(|g| peer_gids.contains(&g));
38 if member(operator_gid) {
39 Some(PeerClass::Operator)
40 } else if member(reader_gid) {
41 Some(PeerClass::Reader)
42 } else {
43 None
44 }
45}
46
47pub fn bind_reader_socket(
58 path: impl AsRef<Path>,
59 mode: u32,
60 reader_group: &str,
61) -> io::Result<OperatorListener> {
62 let path = path.as_ref();
63 let inner = bind_socket_file(path, mode)?;
64 let reader_gid = group_gid(reader_group);
65 set_socket_group(path, reader_group, reader_gid);
66 Ok(OperatorListener {
67 inner,
68 path: path.to_path_buf(),
69 server_uid: nix::unistd::geteuid().as_raw(),
70 operator_gid: group_gid(OPERATOR_GROUP),
71 reader_gid,
72 })
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 const SERVER: u32 = 900;
80 const OPERATOR: u32 = 988;
81 const SENSORS: u32 = 977;
82 const PLUGINS: u32 = 966;
83
84 fn class(uid: u32, gids: &[u32]) -> Option<PeerClass> {
85 classify_peer(uid, gids, SERVER, Some(OPERATOR), Some(SENSORS))
86 }
87
88 #[test]
89 fn service_peers_are_root_and_the_server_uid() {
90 assert_eq!(class(0, &[0]), Some(PeerClass::Service));
91 assert_eq!(class(SERVER, &[SERVER]), Some(PeerClass::Service));
92 }
93
94 #[test]
95 fn the_operator_group_outranks_the_reader_group() {
96 assert_eq!(class(1000, &[1000, OPERATOR]), Some(PeerClass::Operator));
97 assert_eq!(
98 class(1000, &[1000, SENSORS, OPERATOR]),
99 Some(PeerClass::Operator)
100 );
101 }
102
103 #[test]
104 fn a_reader_group_member_is_only_a_reader() {
105 assert_eq!(class(61_001, &[PLUGINS, SENSORS]), Some(PeerClass::Reader));
107 }
108
109 #[test]
110 fn anyone_else_is_refused() {
111 assert_eq!(class(61_001, &[PLUGINS]), None);
112 assert_eq!(class(1000, &[1000]), None);
113 assert_eq!(
115 classify_peer(61_001, &[SENSORS], SERVER, Some(OPERATOR), None),
116 None
117 );
118 assert_eq!(classify_peer(1000, &[OPERATOR], SERVER, None, None), None);
119 }
120
121 #[tokio::test]
124 async fn a_reader_socket_classifies_its_own_uid_as_service() {
125 let dir = tempfile::tempdir().unwrap();
126 let path = dir.path().join("bus.sock");
127 let listener = bind_reader_socket(&path, 0o660, "ados-test-no-such-group").unwrap();
128 let _client = tokio::net::UnixStream::connect(&path).await.unwrap();
129 let (_stream, class) = listener.accept_classified().await.unwrap();
130 assert_eq!(class, PeerClass::Service);
131 }
132}