bread-ecosystem/bread-polkit/src/main.rs
Breadway 2897335016 bread-polkit: only auth as an identity polkit actually offered; fail fast without the lock
- The agent prompt's username field is user-editable. It was passed
  straight to `auth::authenticate`, so a request scoped to specific
  accounts (e.g. root only) could have its PAM conversation redirected
  to any local user. `resolve_user` now accepts only the `unix-user`
  identities from `BeginAuthentication` (empty = the prefilled default);
  anything else re-shows the prompt with an explanation. PAM still has to
  clear polkit's own authorization, but this closes the foot-gun at the
  one place the identity list is known.
- A failed single-instance lock now exits(1) instead of continuing: a
  second agent would `serve_at` the same object path, and a prompt held
  by a process that couldn't take the lock is ambiguous state.
2026-08-31 15:37:52 +08:00

99 lines
3 KiB
Rust

//! bread-polkit — themed PolicyKit authentication agent.
//!
//! Registers on the `org.freedesktop.PolicyKit1.AuthenticationAgent`
//! interface and shows a bread-theme GTK4 password prompt. This is an
//! agent, not a wrapper that execs `polkit-gnome`.
//!
//! Autostart: copy `contrib/bread-polkit.desktop` to
//! `~/.config/autostart/`, or add `exec-once = bread-polkit` to Hyprland.
mod agent;
mod auth;
mod ui;
use bread_app::singleton::Acquire;
use gtk4::prelude::*;
const APP_NAME: &str = "bread-polkit";
fn main() {
let arg = std::env::args().nth(1);
match arg.as_deref() {
Some("-h") | Some("--help") => {
print_help();
return;
}
Some("-V") | Some("--version") => {
println!("bread-polkit {}", env!("CARGO_PKG_VERSION"));
return;
}
Some(other) => {
eprintln!("bread-polkit: unknown argument '{other}'");
print_help();
std::process::exit(2);
}
None => {}
}
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_target(false)
.init();
let _guard = match bread_app::try_acquire(APP_NAME) {
Ok(Acquire::Acquired(g)) => Some(g),
Ok(Acquire::HeldByOther(pid)) => {
eprintln!("bread-polkit: already running (pid {pid:?})");
std::process::exit(0);
}
Err(e) => {
// Don't keep running without the single-instance lock: a second
// copy would attempt to `serve_at` the same PolicyKit agent
// object path on the system bus, and a password prompt held by a
// process whose lock couldn't be taken is ambiguous state. Fail
// fast and let a wrapper/autostart retry.
eprintln!("bread-polkit: singleton lock unavailable ({e}); exiting");
std::process::exit(1);
}
};
let app_id = bread_app::application_id(APP_NAME).expect("static app name");
let app = gtk4::Application::builder().application_id(&app_id).build();
app.connect_activate(|app| {
bread_theme::gtk::apply_shared();
bread_theme::gtk::apply_app_css(ui::app_css);
// No window until polkit asks; hold so GApplication stays alive.
std::mem::forget(app.hold());
if let Err(e) = agent::spawn() {
eprintln!("bread-polkit: {e:#}");
app.quit();
}
});
app.run();
}
fn print_help() {
print!(
"\
bread-polkit — themed PolicyKit authentication agent
Usage:
bread-polkit
bread-polkit --help
bread-polkit --version
Autostart (pick one):
cp contrib/bread-polkit.desktop ~/.config/autostart/
exec-once = bread-polkit # Hyprland
The agent talks to the polkit1 AuthenticationAgent API and prompts for
a password. It does not exec polkit-gnome. Not a bakery product; not
on the BOS ISO lockfile.
"
);
}