use std::collections::HashMap as PolkitMap;
use zbus::blocking::Proxy as BlockingProxy;
use zbus::zvariant::Value as PolkitValue;
use zbus::MessageFlags;
const POLKIT_BUS: &str = "org.freedesktop.PolicyKit1";
const POLKIT_PATH: &str = "/org/freedesktop/PolicyKit1/Authority";
const POLKIT_INTERFACE: &str = "org.freedesktop.PolicyKit1.Authority";
const DBUS_BUS: &str = "org.freedesktop.DBus";
const DBUS_PATH: &str = "/org/freedesktop/DBus";
const DBUS_INTERFACE: &str = "org.freedesktop.DBus";
const POLKIT_ALLOW_USER_INTERACTION: u32 = 1;
#[derive(Debug)]
struct DbusAuthorization {
connection: Connection,
}
impl DbusAuthorization {
fn new() -> zbus::Result<Self> {
Ok(Self {
connection: Connection::system()?,
})
}
fn authorize(
&self,
header: &MessageHeader<'_>,
action: &str,
details: PolkitMap<String, String>,
) -> Result<(), DbusError> {
self.authorize_with_good_user(header, action, details, None)
}
fn authorize_good_user(
&self,
header: &MessageHeader<'_>,
action: &str,
details: PolkitMap<String, String>,
good_user: u32,
) -> Result<(), DbusError> {
self.authorize_with_good_user(header, action, details, Some(good_user))
}
fn authorize_with_good_user(
&self,
header: &MessageHeader<'_>,
action: &str,
details: PolkitMap<String, String>,
good_user: Option<u32>,
) -> Result<(), DbusError> {
let sender = header
.sender()
.map_err(|error| DbusError::AccessDenied(error.to_string()))?
.ok_or_else(|| DbusError::AccessDenied("D-Bus caller has no unique name".to_owned()))?
.as_str();
let sender_uid = self.sender_uid(sender)?;
if sender_uid == 0 || good_user == Some(sender_uid) {
return Ok(());
}
let subject_details = PolkitMap::from([("name", PolkitValue::from(sender))]);
let subject = ("system-bus-name", subject_details);
let flags = if header
.primary()
.flags()
.contains(MessageFlags::AllowInteractiveAuth)
{
POLKIT_ALLOW_USER_INTERACTION
} else {
0
};
let proxy = BlockingProxy::new(
&self.connection,
POLKIT_BUS,
POLKIT_PATH,
POLKIT_INTERFACE,
)
.map_err(|error| DbusError::AccessDenied(error.to_string()))?;
let (authorized, challenge, _): (bool, bool, PolkitMap<String, String>) = proxy
.call(
"CheckAuthorization",
&(subject, action, details, flags, ""),
)
.map_err(|error| DbusError::AccessDenied(error.to_string()))?;
if authorized {
Ok(())
} else if challenge && flags == 0 {
Err(DbusError::InteractiveAuthorizationRequired(
"the operation requires interactive authorization".to_owned(),
))
} else {
Err(DbusError::AccessDenied(format!(
"caller is not authorized for {action}"
)))
}
}
fn sender_uid(&self, sender: &str) -> Result<u32, DbusError> {
let proxy = BlockingProxy::new(
&self.connection,
DBUS_BUS,
DBUS_PATH,
DBUS_INTERFACE,
)
.map_err(|error| DbusError::AccessDenied(error.to_string()))?;
proxy
.call("GetConnectionUnixUser", &(sender,))
.map_err(|error| DbusError::AccessDenied(error.to_string()))
}
}
fn interface_details(resolver: &Resolver, ifindex: i32) -> PolkitMap<String, String> {
let interface = resolver
.link(ifindex)
.and_then(|link| link.kernel.map(|kernel| kernel.ifname))
.filter(|name| !name.is_empty())
.unwrap_or_else(|| ifindex.to_string());
PolkitMap::from([("interface".to_owned(), interface)])
}
fn no_details() -> PolkitMap<String, String> {
PolkitMap::new()
}