roowho2_lib/server/
polkit.rs1use std::collections::HashMap;
4
5use anyhow::Context;
6use zbus::{Connection, proxy, zvariant::Value};
7
8#[proxy(
9 default_service = "org.freedesktop.PolicyKit1",
10 default_path = "/org/freedesktop/PolicyKit1/Authority",
11 interface = "org.freedesktop.PolicyKit1.Authority"
12)]
13trait Authority {
14 #[allow(clippy::type_complexity)]
15 fn check_authorization(
16 &self,
17 subject: &(&str, HashMap<&str, Value<'_>>),
18 action_id: &str,
19 details: &HashMap<&str, &str>,
20 flags: u32,
21 cancellation_id: &str,
22 ) -> zbus::Result<(bool, bool, HashMap<String, String>)>;
23}
24
25#[proxy(
26 default_service = "org.freedesktop.login1",
27 default_path = "/org/freedesktop/login1",
28 interface = "org.freedesktop.login1.Manager"
29)]
30trait LoginManager {
31 #[zbus(name = "GetSessionByPID")]
32 fn get_session_by_pid(&self, pid: u32) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
33}
34
35#[proxy(interface = "org.freedesktop.login1.Session")]
36trait LoginSession {
37 #[zbus(property)]
38 fn id(&self) -> zbus::Result<String>;
39}
40
41#[derive(Clone)]
42pub struct PolkitAuthority {
43 connection: Connection,
44}
45
46impl PolkitAuthority {
47 pub async fn connect() -> anyhow::Result<Self> {
48 let connection = Connection::system()
49 .await
50 .context("failed to connect to the D-Bus system bus")?;
51 Ok(Self { connection })
52 }
53
54 pub fn connection(&self) -> &Connection {
55 &self.connection
56 }
57
58 async fn session_id_for_pid(&self, pid: u32) -> Option<String> {
60 let manager = LoginManagerProxy::new(&self.connection).await.ok()?;
61 let path = manager.get_session_by_pid(pid).await.ok()?;
62 let session = LoginSessionProxy::builder(&self.connection)
63 .path(path)
64 .ok()?
65 .build()
66 .await
67 .ok()?;
68 session.id().await.ok()
69 }
70
71 async fn subject(
73 &self,
74 pid: u32,
75 ) -> anyhow::Result<(&'static str, HashMap<&'static str, Value<'_>>)> {
76 if let Some(session_id) = self.session_id_for_pid(pid).await {
77 let mut details = HashMap::new();
78 details.insert("session-id", Value::from(session_id));
79 return Ok(("unix-session", details));
80 }
81
82 let start_time = process_start_time(pid)?;
83 let mut details = HashMap::new();
84 details.insert("pid", Value::from(pid));
85 details.insert("start-time", Value::from(start_time));
86 Ok(("unix-process", details))
87 }
88
89 pub(crate) async fn check(
91 &self,
92 pid: u32,
93 action_id: &str,
94 details: HashMap<&str, &str>,
95 ) -> anyhow::Result<bool> {
96 let proxy = AuthorityProxy::new(&self.connection)
97 .await
98 .context("failed to build polkit Authority proxy")?;
99
100 let subject = self.subject(pid).await?;
101
102 let (authorized, _interactive, _details) = proxy
103 .check_authorization(&subject, action_id, &details, 0, "")
104 .await
105 .context("CheckAuthorization call failed")?;
106
107 Ok(authorized)
108 }
109}
110
111fn process_start_time(pid: u32) -> anyhow::Result<u64> {
113 let stat = std::fs::read_to_string(format!("/proc/{pid}/stat"))
114 .with_context(|| format!("failed to read /proc/{pid}/stat"))?;
115
116 let after_comm = stat
117 .rsplit_once(')')
118 .with_context(|| format!("malformed /proc/{pid}/stat"))?
119 .1;
120
121 after_comm
123 .split_whitespace()
124 .nth(19)
125 .with_context(|| format!("missing starttime field in /proc/{pid}/stat"))?
126 .parse::<u64>()
127 .context("starttime field is not a valid number")
128}