git.delta.rocks / remowt / refs/commits / 7c2fb57d5b71

difftreelog

refactor more work on this project

nomswqonYaroslav Bolyukin2024-07-31parent: #a05ea00.patch.diff
in: trunk

24 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -27,6 +27,55 @@
 ]
 
 [[package]]
+name = "anstream"
+version = "0.6.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
+dependencies = [
+ "anstyle",
+ "windows-sys",
+]
+
+[[package]]
 name = "anyhow"
 version = "1.0.86"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -301,6 +350,52 @@
 ]
 
 [[package]]
+name = "clap"
+version = "4.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "clap_lex",
+ "strsim",
+]
+
+[[package]]
+name = "clap_derive"
+version = "4.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
+
+[[package]]
 name = "concurrent-queue"
 version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -524,6 +619,12 @@
 checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
 
 [[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
 name = "hermit-abi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -552,6 +653,12 @@
 ]
 
 [[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
 name = "itertools"
 version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -591,6 +698,12 @@
 checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
 
 [[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
 name = "memchr"
 version = "2.7.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -656,6 +769,16 @@
 ]
 
 [[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
+
+[[package]]
 name = "object"
 version = "0.36.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -681,6 +804,12 @@
 ]
 
 [[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
 name = "pam-client"
 version = "0.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -734,6 +863,49 @@
 ]
 
 [[package]]
+name = "polkit-agent"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "pam-client",
+ "polkit-shared",
+ "rand",
+ "serde",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+ "ui-prompt",
+ "uuid",
+ "zbus",
+ "zbus_polkit",
+]
+
+[[package]]
+name = "polkit-backend"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "clap",
+ "nix",
+ "pam-client",
+ "polkit-shared",
+ "tokio",
+ "tracing",
+ "tracing-subscriber",
+ "ui-prompt",
+ "zbus",
+ "zbus_polkit",
+]
+
+[[package]]
+name = "polkit-shared"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "zbus",
+]
+
+[[package]]
 name = "polling"
 version = "3.7.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -839,18 +1011,6 @@
 version = "0.8.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
-
-[[package]]
-name = "remowt"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "pam-client",
- "serde",
- "tokio",
- "zbus",
- "zbus_polkit",
-]
 
 [[package]]
 name = "rpassword"
@@ -955,6 +1115,15 @@
 ]
 
 [[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
 name = "shlex"
 version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -979,6 +1148,12 @@
 ]
 
 [[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+
+[[package]]
 name = "socket2"
 version = "0.5.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -995,6 +1170,12 @@
 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
 [[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
 name = "syn"
 version = "1.0.109"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1029,6 +1210,36 @@
 ]
 
 [[package]]
+name = "thiserror"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.63"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.72",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
 name = "tokio"
 version = "1.39.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1103,9 +1314,35 @@
 checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
 dependencies = [
  "once_cell",
+ "valuable",
 ]
 
 [[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+dependencies = [
+ "nu-ansi-term",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing-core",
+ "tracing-log",
+]
+
+[[package]]
 name = "typenum"
 version = "1.17.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1123,12 +1360,44 @@
 ]
 
 [[package]]
+name = "ui-prompt"
+version = "0.1.0"
+dependencies = [
+ "serde",
+ "thiserror",
+ "tokio",
+ "tracing",
+ "zbus",
+]
+
+[[package]]
 name = "unicode-ident"
 version = "1.0.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
 
 [[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "uuid"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
+
+[[package]]
 name = "version_check"
 version = "0.9.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,3 @@
-[package]
-name = "remowt"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-anyhow = "1.0.86"
-pam-client = "0.5.0"
-serde = { version = "1.0.204", features = ["derive"] }
-tokio = { version = "1.39.2", features = ["rt-multi-thread", "fs", "macros"] }
-zbus = { version = "4.4.0", features = ["tokio"] }
-zbus_polkit = { version = "4.0.0", features = ["tokio"] }
+[workspace]
+members = ["cmds/*", "crates/*"]
+resolver = "2"
addedcmds/polkit-agent/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-agent/Cargo.toml
@@ -0,0 +1,18 @@
+[package]
+name = "polkit-agent"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.86"
+pam-client = "0.5.0"
+polkit-shared = { version = "0.1.0", path = "../../crates/polkit-shared" }
+rand = "0.8.5"
+serde = { version = "1.0.204", features = ["derive"] }
+tokio = { version = "1.39.2", features = ["rt-multi-thread", "fs", "macros"] }
+tracing = "0.1.40"
+tracing-subscriber = "0.3.18"
+ui-prompt = { version = "0.1.0", path = "../../crates/ui-prompt" }
+uuid = { version = "1.10.0", features = ["v4"] }
+zbus = { version = "4.4.0", features = ["tokio"] }
+zbus_polkit = { version = "4.0.0", features = ["tokio"] }
addedcmds/polkit-agent/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-agent/src/main.rs
@@ -0,0 +1,177 @@
+use std::collections::HashMap;
+use std::future;
+use std::marker::PhantomData;
+use std::sync::{Mutex, RwLock};
+
+use polkit_shared::{BackendRequest, Identity};
+use tokio::runtime::Handle;
+use tokio::task::{AbortHandle, JoinHandle, LocalSet};
+use tracing::trace;
+use ui_prompt::dbus::DbusPrompterInterface;
+use ui_prompt::rofi::RofiPrompter;
+use ui_prompt::Prompter;
+use zbus::zvariant::{OwnedValue, Str};
+use zbus::ObjectServer;
+use zbus::{interface, proxy, Connection};
+use zbus_polkit::policykit1::Subject;
+
+struct TemporaryPrompterInterface<P: Prompter + Send + Sync + 'static> {
+    connection: Connection,
+    path: String,
+    _marker: PhantomData<P>,
+}
+impl<P: Prompter + Send + Sync + 'static> TemporaryPrompterInterface<P> {
+    async fn new(connection: Connection, prompter: P) -> Self {
+        let path = format!(
+            "/remowt/prompters/{}",
+            uuid::Uuid::new_v4().to_string().replace("-", "_")
+        );
+        let _ = connection
+            .object_server()
+            .at(path.clone(), DbusPrompterInterface(prompter))
+            .await;
+        Self {
+            connection,
+            path,
+            _marker: PhantomData,
+        }
+    }
+}
+impl<P: Prompter + Send + Sync + 'static> Drop for TemporaryPrompterInterface<P> {
+    fn drop(&mut self) {
+        // FIXME: block_in_place prevents to moving to current_thread runtime
+        // There should be a blocking way to remove ObjectServer listener.
+        // As far as I can see, it is only async because of async RwLock, shouldn't it be
+        // just a sync lock?
+        tokio::task::block_in_place(move || {
+            Handle::current().block_on(async {
+                let _ = self
+                    .connection
+                    .object_server()
+                    .remove::<DbusPrompterInterface<P>, String>(self.path.clone())
+                    .await;
+            });
+        });
+    }
+}
+
+struct Agent {
+    helper: PolkitHelperProxy<'static>,
+    tasks: Mutex<HashMap<String, AbortHandle>>,
+    connection: Connection,
+}
+impl Agent {
+    async fn new(connection: Connection) -> anyhow::Result<Self> {
+        Ok(Self {
+            helper: PolkitHelperProxy::new(&connection).await?,
+            tasks: Mutex::new(HashMap::new()),
+            connection,
+        })
+    }
+}
+
+#[interface(name = "org.freedesktop.PolicyKit1.AuthenticationAgent")]
+impl Agent {
+    /// BeginAuthentication method
+    #[allow(clippy::too_many_arguments)]
+    async fn begin_authentication(
+        &mut self,
+        action_id: String,
+        message: String,
+        icon_name: String,
+        details: HashMap<String, String>,
+        cookie: String,
+        identities: Vec<Identity>,
+    ) -> zbus::fdo::Result<()> {
+        trace!("begin auth");
+        let task = {
+            let connection = self.connection.clone();
+            let helper = self.helper.clone();
+            let cookie = cookie.clone();
+            tokio::task::spawn(async move {
+                trace!("conversation task");
+                let prompter = TemporaryPrompterInterface::new(connection, RofiPrompter).await;
+                helper
+                    .init_conversation(
+                        BackendRequest {
+                            cookie: cookie.to_owned(),
+                            environment: HashMap::new(),
+                            prompter_path: prompter.path.clone(),
+                            // TODO: Let user choose
+                            identity: identities.get(0).expect("first always exists").clone(),
+                        }, // cookie.to_owned(), HashMap::new(), prompter.path.clone()
+                    )
+                    .await?;
+                println!("ASKED");
+                dbg!(action_id, message, icon_name, details, cookie, identities);
+
+                Ok(())
+            })
+        };
+
+        self.tasks
+            .lock()
+            .unwrap()
+            .insert(cookie.clone(), task.abort_handle());
+        let result = task.await.expect("join error");
+        // The only way to no reach this line, is to either panic in previous line, or if authorization cancelled,
+        // while cancellation will remove task by itself.
+        // TODO: But still it would be better to have abort guard, which will remove it from HashMap
+        self.tasks.lock().unwrap().remove(&cookie);
+
+        result
+    }
+
+    /// CancelAuthentication method
+    async fn cancel_authentication(&self, cookie: &str) -> zbus::fdo::Result<()> {
+        trace!("cancel auth");
+        if let Some(abort) = self.tasks.lock().unwrap().remove(cookie) {
+            abort.abort();
+        }
+        // debug!("Authentication cancled ! {cookie}");
+        Ok(())
+    }
+}
+
+const OBJ_PATH: &str = "/0lach/polkitAgent";
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+    tracing_subscriber::fmt::init();
+
+    trace!("started");
+    let conn = Connection::system().await?;
+
+    let proxy = zbus_polkit::policykit1::AuthorityProxy::new(&conn).await?;
+    conn.object_server()
+        .at(OBJ_PATH, Agent::new(conn.clone()).await?)
+        .await?;
+
+    let session_id = std::env::var("XDG_SESSION_ID")?;
+    let mut details = HashMap::new();
+    let val: OwnedValue = {
+        let wrapped: Str<'_> = session_id.into();
+        wrapped.into()
+    };
+    details.insert("session-id".to_string(), val);
+    proxy
+        .register_authentication_agent(
+            &Subject {
+                subject_kind: "unix-session".to_string(),
+                subject_details: details,
+            },
+            "C",
+            OBJ_PATH,
+        )
+        .await?;
+
+    future::pending().await
+}
+
+#[proxy(
+    interface = "lach.PolkitHelper",
+    default_service = "lach.polkit.helper1",
+    default_path = "/lach/PolkitHelper"
+)]
+trait PolkitHelper {
+    fn init_conversation(&self, request: BackendRequest) -> zbus::Result<()>;
+}
addedcmds/polkit-backend/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-backend/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "polkit-backend"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+anyhow = "1.0.86"
+clap = { version = "4.5.11", features = ["derive"] }
+nix = "0.29.0"
+pam-client = "0.5.0"
+polkit-shared = { version = "0.1.0", path = "../../crates/polkit-shared" }
+tokio = { version = "1.39.2", features = ["macros", "rt", "rt-multi-thread"] }
+tracing = "0.1.40"
+tracing-subscriber = "0.3.18"
+ui-prompt = { version = "0.1.0", path = "../../crates/ui-prompt" }
+zbus = { version = "4.4.0", features = ["tokio"] }
+zbus_polkit = { version = "4.0.0", features = ["tokio"] }
addedcmds/polkit-backend/etc/systemd/system/remowt-polkit-helper.servicediffbeforeafterboth

no changes

addedcmds/polkit-backend/share/dbus-1/system-services/lach.polkit.helper1.confdiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-backend/share/dbus-1/system-services/lach.polkit.helper1.conf
@@ -0,0 +1,5 @@
+[D-BUS Service]
+Name=lach.polkit.helper1
+Exec=/bin/false
+User=root
+SystemdService=dbus-lach.polkit.helper1.service
addedcmds/polkit-backend/share/dbus-1/system.d/lach.polkit.helper1.confdiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-backend/share/dbus-1/system.d/lach.polkit.helper1.conf
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "https://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
+<busconfig>
+	<policy user="root">
+		<allow own = "lach.polkit.helper1"/>
+		<allow send_interface="lach.PolkitInputHandler"/>
+	</policy>
+	<policy context="default">
+		<allow send_destination="lach.polkit.helper1"/>
+		<deny send_interface="lach.PolkitInputHandler"/>
+	</policy>
+</busconfig>
addedcmds/polkit-backend/src/main.rsdiffbeforeafterboth
--- /dev/null
+++ b/cmds/polkit-backend/src/main.rs
@@ -0,0 +1,238 @@
+use std::collections::{HashMap, HashSet};
+use std::ffi::{CStr, CString};
+use std::future::pending;
+use std::sync::LazyLock;
+
+use anyhow::Context as _;
+use clap::Parser;
+use nix::unistd::{setuid, Uid, User};
+use pam_client::{Context, ConversationHandler, ErrorCode, Flag};
+use polkit_shared::BackendRequest;
+use tokio::runtime::Handle;
+use tokio::task::{block_in_place, spawn_blocking};
+use tracing::trace;
+use ui_prompt::dbus::DbusPrompterProxyBlocking;
+use ui_prompt::{BlockingPrompter, Prompter};
+use zbus::fdo;
+use zbus::message::Header;
+use zbus::zvariant::OwnedValue;
+use zbus::{blocking, interface, proxy, Connection};
+
+struct Helper {
+    connection: Connection,
+    blocking_connection: blocking::Connection,
+}
+
+static ALLOWED_ENVIRONMENT: LazyLock<HashSet<&str>> = LazyLock::new(|| {
+    [
+        // pam ssh agent auth
+        "SSH_AUTH_SOCK",
+        // ssh itself provides this when running PAM
+        "SSH_AUTH_INFO_0",
+        // contains user which ran sudo
+        "SUDO_USER",
+    ]
+    .into_iter()
+    .collect()
+});
+
+struct Conversation<P>(P);
+impl<P: BlockingPrompter> Conversation<P> {
+    fn prompt_inner(&self, echo: bool, prompt: &CStr) -> Result<CString, ErrorCode> {
+        trace!("do prompt");
+        let out = self
+            .0
+            .prompt_text(
+                echo,
+                &prompt.to_string_lossy(),
+                "Polkit prompt request",
+                &[],
+            )
+            .map_err(|e| {
+                trace!("prompt error: {e}");
+                ErrorCode::CONV_ERR
+            })?;
+        CString::new(out).map_err(|_| ErrorCode::CONV_AGAIN)
+    }
+    fn text_inner(&self, error: bool, msg: &CStr) {
+        trace!("do text");
+        let msg = msg.to_string_lossy();
+        let _ = self.0.display_text(error, &msg, &[]);
+    }
+}
+impl<P: BlockingPrompter> ConversationHandler for Conversation<P> {
+    fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
+        self.prompt_inner(true, prompt)
+    }
+
+    fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
+        self.prompt_inner(false, prompt)
+    }
+
+    fn text_info(&mut self, msg: &CStr) {
+        self.text_inner(false, msg)
+    }
+
+    fn error_msg(&mut self, msg: &CStr) {
+        self.text_inner(true, msg)
+    }
+
+    fn radio_prompt(&mut self, prompt: &CStr) -> Result<bool, ErrorCode> {
+        let prompt = prompt.to_string_lossy();
+        let result = self
+            .0
+            .prompt_radio(&prompt, "Polkit prompt request", &[])
+            .map_err(|_| ErrorCode::CONV_ERR)?;
+        Ok(result)
+    }
+}
+
+#[proxy(
+    default_service = "org.freedesktop.DBus",
+    default_path = "/org/freedesktop/DBus"
+)]
+trait DBus {
+    fn get_connection_credentials(&self, body: &str) -> zbus::Result<HashMap<String, OwnedValue>>;
+}
+
+#[interface(name = "lach.PolkitHelper")]
+impl Helper {
+    async fn init_conversation(
+        &self,
+        request: BackendRequest,
+        #[zbus(header)] hdr: Header<'_>,
+    ) -> fdo::Result<()> {
+        let Some(sender) = hdr.sender().map(|v| v.to_owned()) else {
+            trace!("missing sender");
+            return Err(fdo::Error::AuthFailed("missing sender".to_owned()));
+        };
+
+        let dbus = DBusProxy::new(&self.connection).await?;
+
+        // TOCTOU: sender might be already disconnected, and there might be another
+        // user with different user id here, but does it matters?
+        let reply = dbus.get_connection_credentials(&sender).await?;
+        let uid: u32 = (&reply["UnixUserID"]).try_into().unwrap();
+
+        let blocking_connection = self.blocking_connection.clone();
+        let thread_result: fdo::Result<()> = block_in_place(move || {
+            trace!("find user");
+            let user = User::from_uid(Uid::from_raw(uid))
+                .map_err(|_| fdo::Error::AuthFailed("error querying user".to_owned()))?
+                .ok_or_else(|| fdo::Error::AuthFailed("uid not found".to_owned()))?;
+
+            let responder = DbusPrompterProxyBlocking::new(
+                &blocking_connection,
+                sender,
+                request.prompter_path,
+            )?;
+            let conversation = Conversation(responder);
+            trace!("run context for {}", &user.name);
+            let mut ctx = Context::new(
+                // TODO: Should another scope be used?
+                "login",
+                Some(&user.name),
+                conversation,
+            )
+            .map_err(|_| fdo::Error::Failed("pam context init failed".to_owned()))?;
+
+            trace!("fill env");
+            for (k, v) in request.environment {
+                if k.contains('=') || !ALLOWED_ENVIRONMENT.contains(k.as_str()) {
+                    continue;
+                }
+                let _ = ctx.putenv(format!("{k}={v}"));
+            }
+
+            trace!("authenticate");
+            ctx.authenticate(Flag::NONE)
+                .map_err(|_| fdo::Error::AuthFailed("pam authentication failed".to_owned()))?;
+
+            trace!("acct mgmt");
+            ctx.acct_mgmt(Flag::NONE)
+                .map_err(|_| fdo::Error::AuthFailed("pam acct mgmt failed".to_owned()))?;
+
+            Ok(())
+        });
+
+        thread_result?;
+
+        trace!("respond");
+        let proxy = zbus_polkit::policykit1::AuthorityProxy::new(&self.connection).await?;
+
+        let identity_details = request
+            .identity
+            .details
+            .iter()
+            .map(|(k, v)| (k.as_str(), (**v).try_clone().expect("success")))
+            .collect::<HashMap<_, _>>();
+        proxy
+            .authentication_agent_response2(
+                uid,
+                &request.cookie,
+                &zbus_polkit::policykit1::Identity {
+                    identity_kind: &request.identity.kind,
+                    identity_details: &identity_details,
+                },
+            )
+            .await?;
+        Ok(())
+    }
+}
+
+const OBJ_PATH: &str = "/lach/PolkitHelper";
+
+#[derive(Parser)]
+struct Opts {
+    /// Not recommended: start as a session connection, then use escalation
+    /// to respond to polkit requests.
+    #[arg(long)]
+    session: bool,
+}
+
+#[tokio::main]
+async fn main() -> anyhow::Result<()> {
+    tracing_subscriber::fmt::init();
+    let opts = Opts::parse();
+    let connection = if opts.session {
+        Connection::session().await
+    } else {
+        Connection::system().await
+    }
+    .context("failed to open connection")?;
+
+    let session = opts.session;
+    let blocking_connection: anyhow::Result<blocking::Connection> = spawn_blocking(move || {
+        Ok(if session {
+            blocking::Connection::session()?
+        } else {
+            blocking::Connection::system()?
+        })
+    })
+    .await?;
+    let blocking_connection = blocking_connection.context("failed to open blocking connection")?;
+
+    if opts.session {
+        setuid(Uid::from_raw(0))
+            .context("polkit-backend needs to be suid if run in session mode")?;
+    }
+
+    connection
+        .object_server()
+        .at(
+            OBJ_PATH,
+            Helper {
+                connection: connection.clone(),
+                blocking_connection,
+            },
+        )
+        .await
+        .context("failed listen path")?;
+
+    connection
+        .request_name("lach.polkit.helper1")
+        .await
+        .context("failed to request name")?;
+
+    pending().await
+}
deletedcmds/polkit-helper/Cargo.lockdiffbeforeafterboth
--- a/cmds/polkit-helper/Cargo.lock
+++ /dev/null
@@ -1,1488 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "addr2line"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
-dependencies = [
- "gimli",
-]
-
-[[package]]
-name = "adler"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
-[[package]]
-name = "aho-corasick"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "anstream"
-version = "0.6.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
-dependencies = [
- "anstyle",
- "anstyle-parse",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle"
-version = "1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
-
-[[package]]
-name = "anstyle-parse"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
-dependencies = [
- "utf8parse",
-]
-
-[[package]]
-name = "anstyle-query"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
-dependencies = [
- "windows-sys",
-]
-
-[[package]]
-name = "anstyle-wincon"
-version = "3.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
-dependencies = [
- "anstyle",
- "windows-sys",
-]
-
-[[package]]
-name = "anyhow"
-version = "1.0.86"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
-
-[[package]]
-name = "async-broadcast"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e"
-dependencies = [
- "event-listener",
- "event-listener-strategy",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-channel"
-version = "2.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a"
-dependencies = [
- "concurrent-queue",
- "event-listener-strategy",
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-executor"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
-dependencies = [
- "async-task",
- "concurrent-queue",
- "fastrand",
- "futures-lite",
- "slab",
-]
-
-[[package]]
-name = "async-fs"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
-dependencies = [
- "async-lock",
- "blocking",
- "futures-lite",
-]
-
-[[package]]
-name = "async-io"
-version = "2.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d6baa8f0178795da0e71bc42c9e5d13261aac7ee549853162e66a241ba17964"
-dependencies = [
- "async-lock",
- "cfg-if",
- "concurrent-queue",
- "futures-io",
- "futures-lite",
- "parking",
- "polling",
- "rustix",
- "slab",
- "tracing",
- "windows-sys",
-]
-
-[[package]]
-name = "async-lock"
-version = "3.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
-dependencies = [
- "event-listener",
- "event-listener-strategy",
- "pin-project-lite",
-]
-
-[[package]]
-name = "async-process"
-version = "2.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7eda79bbd84e29c2b308d1dc099d7de8dcc7035e48f4bf5dc4a531a44ff5e2a"
-dependencies = [
- "async-channel",
- "async-io",
- "async-lock",
- "async-signal",
- "async-task",
- "blocking",
- "cfg-if",
- "event-listener",
- "futures-lite",
- "rustix",
- "tracing",
- "windows-sys",
-]
-
-[[package]]
-name = "async-recursion"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "async-signal"
-version = "0.2.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfb3634b73397aa844481f814fad23bbf07fdb0eabec10f2eb95e58944b1ec32"
-dependencies = [
- "async-io",
- "async-lock",
- "atomic-waker",
- "cfg-if",
- "futures-core",
- "futures-io",
- "rustix",
- "signal-hook-registry",
- "slab",
- "windows-sys",
-]
-
-[[package]]
-name = "async-task"
-version = "4.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
-
-[[package]]
-name = "async-trait"
-version = "0.1.81"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "atomic-waker"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
-
-[[package]]
-name = "autocfg"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
-
-[[package]]
-name = "backtrace"
-version = "0.3.73"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
-dependencies = [
- "addr2line",
- "cc",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
-]
-
-[[package]]
-name = "bindgen"
-version = "0.69.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
-dependencies = [
- "bitflags 2.6.0",
- "cexpr",
- "clang-sys",
- "itertools",
- "lazy_static",
- "lazycell",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
-
-[[package]]
-name = "bitflags"
-version = "2.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
-
-[[package]]
-name = "block-buffer"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
-dependencies = [
- "generic-array",
-]
-
-[[package]]
-name = "blocking"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea"
-dependencies = [
- "async-channel",
- "async-task",
- "futures-io",
- "futures-lite",
- "piper",
-]
-
-[[package]]
-name = "bytes"
-version = "1.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952"
-
-[[package]]
-name = "cc"
-version = "1.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
-
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "cfg_aliases"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
-
-[[package]]
-name = "clang-sys"
-version = "1.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
-dependencies = [
- "glob",
- "libc",
-]
-
-[[package]]
-name = "clap"
-version = "4.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
-dependencies = [
- "clap_builder",
- "clap_derive",
-]
-
-[[package]]
-name = "clap_builder"
-version = "4.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
-dependencies = [
- "anstream",
- "anstyle",
- "clap_lex",
- "strsim",
-]
-
-[[package]]
-name = "clap_derive"
-version = "4.5.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
-dependencies = [
- "heck",
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
-
-[[package]]
-name = "colorchoice"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
-
-[[package]]
-name = "concurrent-queue"
-version = "2.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
-dependencies = [
- "crossbeam-utils",
-]
-
-[[package]]
-name = "cpufeatures"
-version = "0.2.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.8.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
-
-[[package]]
-name = "crypto-common"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
-dependencies = [
- "generic-array",
- "typenum",
-]
-
-[[package]]
-name = "digest"
-version = "0.10.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
-dependencies = [
- "block-buffer",
- "crypto-common",
-]
-
-[[package]]
-name = "either"
-version = "1.13.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
-
-[[package]]
-name = "endi"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
-
-[[package]]
-name = "enum-repr"
-version = "0.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bad30c9c0fa1aaf1ae5010dab11f1117b15d35faf62cda4bbbc53b9987950f18"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
-]
-
-[[package]]
-name = "enumflags2"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d"
-dependencies = [
- "enumflags2_derive",
- "serde",
-]
-
-[[package]]
-name = "enumflags2_derive"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "equivalent"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
-
-[[package]]
-name = "errno"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
-dependencies = [
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "event-listener"
-version = "5.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
-dependencies = [
- "concurrent-queue",
- "parking",
- "pin-project-lite",
-]
-
-[[package]]
-name = "event-listener-strategy"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
-dependencies = [
- "event-listener",
- "pin-project-lite",
-]
-
-[[package]]
-name = "fastrand"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
-
-[[package]]
-name = "futures-core"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
-
-[[package]]
-name = "futures-io"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
-
-[[package]]
-name = "futures-lite"
-version = "2.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5"
-dependencies = [
- "fastrand",
- "futures-core",
- "futures-io",
- "parking",
- "pin-project-lite",
-]
-
-[[package]]
-name = "futures-sink"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
-
-[[package]]
-name = "futures-task"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
-
-[[package]]
-name = "futures-util"
-version = "0.3.30"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
-dependencies = [
- "futures-core",
- "futures-io",
- "futures-sink",
- "futures-task",
- "memchr",
- "pin-project-lite",
- "pin-utils",
- "slab",
-]
-
-[[package]]
-name = "generic-array"
-version = "0.14.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
-dependencies = [
- "typenum",
- "version_check",
-]
-
-[[package]]
-name = "getrandom"
-version = "0.2.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
-dependencies = [
- "cfg-if",
- "libc",
- "wasi",
-]
-
-[[package]]
-name = "gimli"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
-
-[[package]]
-name = "glob"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
-
-[[package]]
-name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-
-[[package]]
-name = "heck"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-
-[[package]]
-name = "hermit-abi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
-
-[[package]]
-name = "hermit-abi"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
-
-[[package]]
-name = "hex"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
-
-[[package]]
-name = "indexmap"
-version = "2.2.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
-dependencies = [
- "equivalent",
- "hashbrown",
-]
-
-[[package]]
-name = "is_terminal_polyfill"
-version = "1.70.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
-
-[[package]]
-name = "itertools"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
-dependencies = [
- "either",
-]
-
-[[package]]
-name = "itoa"
-version = "1.0.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
-
-[[package]]
-name = "lazy_static"
-version = "1.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
-
-[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
-[[package]]
-name = "libc"
-version = "0.2.155"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
-
-[[package]]
-name = "linux-raw-sys"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
-
-[[package]]
-name = "memchr"
-version = "2.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
-
-[[package]]
-name = "memoffset"
-version = "0.9.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "minimal-lexical"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-
-[[package]]
-name = "miniz_oxide"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
-dependencies = [
- "adler",
-]
-
-[[package]]
-name = "mio"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
-dependencies = [
- "hermit-abi 0.3.9",
- "libc",
- "wasi",
- "windows-sys",
-]
-
-[[package]]
-name = "nix"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
-dependencies = [
- "bitflags 2.6.0",
- "cfg-if",
- "cfg_aliases",
- "libc",
- "memoffset",
-]
-
-[[package]]
-name = "nom"
-version = "7.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
-dependencies = [
- "memchr",
- "minimal-lexical",
-]
-
-[[package]]
-name = "object"
-version = "0.36.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "once_cell"
-version = "1.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
-
-[[package]]
-name = "ordered-stream"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
-dependencies = [
- "futures-core",
- "pin-project-lite",
-]
-
-[[package]]
-name = "pam-client"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51bd776116a7ada5ebbe31f54cdc5b1030ed7265686cf7c8a21c057a2f8dab9a"
-dependencies = [
- "bitflags 1.3.2",
- "enum-repr",
- "libc",
- "pam-sys",
- "rpassword",
- "rustversion",
-]
-
-[[package]]
-name = "pam-sys"
-version = "1.0.0-alpha5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce9484729b3e52c0bacdc5191cb6a6a5f31ef4c09c5e4ab1209d3340ad9e997b"
-dependencies = [
- "bindgen",
- "libc",
-]
-
-[[package]]
-name = "parking"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
-
-[[package]]
-name = "pin-project-lite"
-version = "0.2.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
-
-[[package]]
-name = "pin-utils"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
-
-[[package]]
-name = "piper"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae1d5c74c9876f070d3e8fd503d748c7d974c3e48da8f41350fa5222ef9b4391"
-dependencies = [
- "atomic-waker",
- "fastrand",
- "futures-io",
-]
-
-[[package]]
-name = "polkit-helper"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "clap",
- "nix",
- "pam-client",
- "tokio",
- "zbus",
- "zbus_polkit",
-]
-
-[[package]]
-name = "polling"
-version = "3.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3ed00ed3fbf728b5816498ecd316d1716eecaced9c0c8d2c5a6740ca214985b"
-dependencies = [
- "cfg-if",
- "concurrent-queue",
- "hermit-abi 0.4.0",
- "pin-project-lite",
- "rustix",
- "tracing",
- "windows-sys",
-]
-
-[[package]]
-name = "ppv-lite86"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
-
-[[package]]
-name = "proc-macro-crate"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
-dependencies = [
- "toml_edit",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.86"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "rand"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
-dependencies = [
- "libc",
- "rand_chacha",
- "rand_core",
-]
-
-[[package]]
-name = "rand_chacha"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
-dependencies = [
- "ppv-lite86",
- "rand_core",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
-dependencies = [
- "getrandom",
-]
-
-[[package]]
-name = "regex"
-version = "1.10.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.4.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.8.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
-
-[[package]]
-name = "rpassword"
-version = "6.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956"
-dependencies = [
- "libc",
- "serde",
- "serde_json",
- "winapi",
-]
-
-[[package]]
-name = "rustc-demangle"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
-
-[[package]]
-name = "rustc-hash"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
-
-[[package]]
-name = "rustix"
-version = "0.38.34"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
-dependencies = [
- "bitflags 2.6.0",
- "errno",
- "libc",
- "linux-raw-sys",
- "windows-sys",
-]
-
-[[package]]
-name = "rustversion"
-version = "1.0.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
-
-[[package]]
-name = "ryu"
-version = "1.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
-
-[[package]]
-name = "serde"
-version = "1.0.204"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
-dependencies = [
- "serde_derive",
-]
-
-[[package]]
-name = "serde_derive"
-version = "1.0.204"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "serde_json"
-version = "1.0.120"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
-dependencies = [
- "itoa",
- "ryu",
- "serde",
-]
-
-[[package]]
-name = "serde_repr"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "sha1"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if",
- "cpufeatures",
- "digest",
-]
-
-[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
-name = "signal-hook-registry"
-version = "1.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "slab"
-version = "0.4.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
-dependencies = [
- "autocfg",
-]
-
-[[package]]
-name = "socket2"
-version = "0.5.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
-dependencies = [
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
-[[package]]
-name = "strsim"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-
-[[package]]
-name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
-version = "2.0.72"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "tempfile"
-version = "3.10.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1"
-dependencies = [
- "cfg-if",
- "fastrand",
- "rustix",
- "windows-sys",
-]
-
-[[package]]
-name = "tokio"
-version = "1.39.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1"
-dependencies = [
- "backtrace",
- "bytes",
- "libc",
- "mio",
- "pin-project-lite",
- "signal-hook-registry",
- "socket2",
- "tokio-macros",
- "tracing",
- "windows-sys",
-]
-
-[[package]]
-name = "tokio-macros"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "toml_datetime"
-version = "0.6.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
-
-[[package]]
-name = "toml_edit"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
-dependencies = [
- "indexmap",
- "toml_datetime",
- "winnow",
-]
-
-[[package]]
-name = "tracing"
-version = "0.1.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
-dependencies = [
- "pin-project-lite",
- "tracing-attributes",
- "tracing-core",
-]
-
-[[package]]
-name = "tracing-attributes"
-version = "0.1.27"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
-
-[[package]]
-name = "tracing-core"
-version = "0.1.32"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
-dependencies = [
- "once_cell",
-]
-
-[[package]]
-name = "typenum"
-version = "1.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
-
-[[package]]
-name = "uds_windows"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
-dependencies = [
- "memoffset",
- "tempfile",
- "winapi",
-]
-
-[[package]]
-name = "unicode-ident"
-version = "1.0.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
-
-[[package]]
-name = "utf8parse"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
-
-[[package]]
-name = "version_check"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-
-[[package]]
-name = "wasi"
-version = "0.11.0+wasi-snapshot-preview1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets",
-]
-
-[[package]]
-name = "windows-targets"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
-dependencies = [
- "windows_aarch64_gnullvm",
- "windows_aarch64_msvc",
- "windows_i686_gnu",
- "windows_i686_gnullvm",
- "windows_i686_msvc",
- "windows_x86_64_gnu",
- "windows_x86_64_gnullvm",
- "windows_x86_64_msvc",
-]
-
-[[package]]
-name = "windows_aarch64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
-
-[[package]]
-name = "windows_i686_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.52.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
-
-[[package]]
-name = "winnow"
-version = "0.5.40"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
-dependencies = [
- "memchr",
-]
-
-[[package]]
-name = "xdg-home"
-version = "1.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca91dcf8f93db085f3a0a29358cd0b9d670915468f4290e8b85d118a34211ab8"
-dependencies = [
- "libc",
- "windows-sys",
-]
-
-[[package]]
-name = "zbus"
-version = "4.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
-dependencies = [
- "async-broadcast",
- "async-executor",
- "async-fs",
- "async-io",
- "async-lock",
- "async-process",
- "async-recursion",
- "async-task",
- "async-trait",
- "blocking",
- "enumflags2",
- "event-listener",
- "futures-core",
- "futures-sink",
- "futures-util",
- "hex",
- "nix",
- "ordered-stream",
- "rand",
- "serde",
- "serde_repr",
- "sha1",
- "static_assertions",
- "tokio",
- "tracing",
- "uds_windows",
- "windows-sys",
- "xdg-home",
- "zbus_macros",
- "zbus_names",
- "zvariant",
-]
-
-[[package]]
-name = "zbus_macros"
-version = "4.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.72",
- "zvariant_utils",
-]
-
-[[package]]
-name = "zbus_names"
-version = "3.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
-dependencies = [
- "serde",
- "static_assertions",
- "zvariant",
-]
-
-[[package]]
-name = "zbus_polkit"
-version = "4.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00a29bfa927b29f91b7feb4e1990f2dd1b4604072f493dc2f074cf59e4e0ba90"
-dependencies = [
- "enumflags2",
- "serde",
- "serde_repr",
- "static_assertions",
- "zbus",
-]
-
-[[package]]
-name = "zvariant"
-version = "4.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
-dependencies = [
- "endi",
- "enumflags2",
- "serde",
- "static_assertions",
- "zvariant_derive",
-]
-
-[[package]]
-name = "zvariant_derive"
-version = "4.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
-dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote",
- "syn 2.0.72",
- "zvariant_utils",
-]
-
-[[package]]
-name = "zvariant_utils"
-version = "2.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.72",
-]
deletedcmds/polkit-helper/Cargo.tomldiffbeforeafterboth
--- a/cmds/polkit-helper/Cargo.toml
+++ /dev/null
@@ -1,13 +0,0 @@
-[package]
-name = "polkit-helper"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-anyhow = "1.0.86"
-clap = { version = "4.5.11", features = ["derive"] }
-nix = "0.29.0"
-pam-client = "0.5.0"
-tokio = { version = "1.39.2", features = ["macros", "rt"] }
-zbus = { version = "4.4.0", features = ["tokio"] }
-zbus_polkit = { version = "4.0.0", features = ["tokio"] }
deletedcmds/polkit-helper/src/main.rsdiffbeforeafterboth
--- a/cmds/polkit-helper/src/main.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-use std::collections::{HashMap, HashSet};
-use std::ffi::{CStr, CString};
-use std::future::pending;
-use std::sync::LazyLock;
-
-use anyhow::Context as _;
-use clap::Parser;
-use nix::unistd::{setuid, Uid, User};
-use pam_client::{Context, ConversationHandler, ErrorCode};
-use tokio::task::spawn_blocking;
-use zbus::blocking;
-use zbus::message::Header;
-use zbus::zvariant::OwnedValue;
-use zbus::{
-    blocking::Connection as BlockingConnection,
-    fdo::{Error as FdoError, Result as FdoResult},
-    interface, proxy, Connection,
-};
-
-struct Helper {
-    connection: Connection,
-    blocking_connection: BlockingConnection,
-}
-
-static ALLOWED_ENVIRONMENT: LazyLock<HashSet<&str>> = LazyLock::new(|| {
-    [
-        // pam ssh agent auth
-        "SSH_AUTH_SOCK",
-        // ssh itself provides this when running PAM
-        "SSH_AUTH_INFO_0",
-        // contains user which ran sudo
-        "SUDO_USER",
-    ]
-    .into_iter()
-    .collect()
-});
-
-struct Conversation {
-    responder: PolkitResponderProxyBlocking<'static>,
-}
-impl Conversation {
-    fn prompt_inner(&self, echo: bool, prompt: &CStr) -> Result<CString, ErrorCode> {
-        let out = self
-            .responder
-            .prompt(echo, &prompt.to_string_lossy())
-            .map_err(|_| ErrorCode::CONV_ERR)?;
-        Ok(CString::new(out).map_err(|_| ErrorCode::CONV_AGAIN)?)
-    }
-    fn text_inner(&self, error: bool, msg: &CStr) {
-        let msg = msg.to_string_lossy();
-        let _ = self.responder.text(error, &msg);
-    }
-}
-impl ConversationHandler for Conversation {
-    fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
-        self.prompt_inner(true, prompt)
-    }
-
-    fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
-        self.prompt_inner(false, prompt)
-    }
-
-    fn text_info(&mut self, msg: &CStr) {
-        self.text_inner(false, msg)
-    }
-
-    fn error_msg(&mut self, msg: &CStr) {
-        self.text_inner(true, msg)
-    }
-
-    fn radio_prompt(&mut self, prompt: &CStr) -> Result<bool, ErrorCode> {
-        let prompt = prompt.to_string_lossy();
-        let result = self
-            .responder
-            .radio(&prompt)
-            .map_err(|_| ErrorCode::CONV_ERR)?;
-        Ok(result)
-    }
-}
-
-#[proxy(
-    default_service = "org.freedesktop.DBus",
-    default_path = "/org/freedesktop/DBus"
-)]
-trait DBus {
-    fn get_connection_credentials(&self, body: &str) -> zbus::Result<HashMap<String, OwnedValue>>;
-}
-
-#[proxy(default_path = "/lach/PolkitResponder")]
-trait PolkitResponder {
-    fn prompt(&self, echo: bool, prompt: &str) -> zbus::Result<String>;
-    fn text(&self, error: bool, msg: &str) -> zbus::Result<()>;
-    fn radio(&self, msg: &str) -> zbus::Result<bool>;
-}
-
-#[interface(name = "lach.PolkitHelper")]
-impl Helper {
-    async fn init_conversation(
-        &self,
-        environment: HashMap<String, String>,
-        #[zbus(header)] hdr: Header<'_>,
-    ) -> zbus::fdo::Result<()> {
-        let Some(sender) = hdr.sender().map(|v| v.to_owned()) else {
-            return Err(zbus::fdo::Error::AuthFailed("missing sender".to_owned()));
-        };
-
-        let dbus = DBusProxy::new(&self.connection).await?;
-
-        let reply = dbus
-            .get_connection_credentials("org.freedesktop.DBus")
-            .await?;
-        let uid: u32 = (&reply["UnixUserID"]).try_into().unwrap();
-
-        let blocking_connection = self.blocking_connection.clone();
-        let thread_result: FdoResult<()> = spawn_blocking(move || {
-            let user = User::from_uid(Uid::from_raw(uid))
-                .map_err(|_| zbus::fdo::Error::AuthFailed("error querying user".to_owned()))?
-                .ok_or_else(|| zbus::fdo::Error::AuthFailed("uid not found".to_owned()))?;
-
-            let responder = PolkitResponderProxyBlocking::new(&blocking_connection, sender)?;
-            let conversation = Conversation { responder };
-            let mut ctx = Context::new(
-                // TODO: Should another scope be used?
-                "login",
-                Some(&user.name),
-                conversation,
-            )
-            .map_err(|_| FdoError::Failed("pam context init failed".to_owned()))?;
-
-            for (k, v) in environment {
-                if k.contains('=') || !ALLOWED_ENVIRONMENT.contains(k.as_str()) {
-                    continue;
-                }
-                let _ = ctx.putenv(format!("{k}={v}"));
-            }
-
-            Ok(())
-        })
-        .await
-        .map_err(|_| FdoError::Failed("thread spawn failed".to_owned()))?;
-
-        thread_result?;
-        // Context::new(hdr.)
-        //
-        Ok(())
-    }
-}
-
-const OBJ_PATH: &str = "/lach/polkitHelper";
-
-#[derive(Parser)]
-struct Opts {
-    /// Not recommended: start as a session connection, then use escalation
-    /// to respond to polkit requests.
-    #[arg(long)]
-    session: bool,
-}
-
-#[tokio::main(flavor = "current_thread")]
-async fn main() -> anyhow::Result<()> {
-    let opts = Opts::parse();
-    let connection = if opts.session {
-        Connection::session().await
-    } else {
-        Connection::system().await
-    }
-    .context("failed to open connection")?;
-
-    let session = opts.session;
-    let blocking_connection: anyhow::Result<BlockingConnection> = spawn_blocking(move || {
-        Ok(if session {
-            BlockingConnection::session()?
-        } else {
-            BlockingConnection::system()?
-        })
-    })
-    .await?;
-    let blocking_connection = blocking_connection.context("failed to open blocking connection")?;
-
-    if opts.session {
-        setuid(Uid::from_raw(0))
-            .context("polkit-helper needs to be suid if run in session mode")?;
-    }
-
-    connection
-        .object_server()
-        .at(
-            OBJ_PATH,
-            Helper {
-                connection: connection.clone(),
-                blocking_connection,
-            },
-        )
-        .await
-        .context("failed listen path")?;
-
-    connection
-        .request_name("lach.PolkitHelper")
-        .await
-        .context("failed to request name")?;
-
-    pending().await
-}
addedcrates/polkit-shared/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/polkit-shared/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "polkit-shared"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = { version = "1.0.204", features = ["derive"] }
+zbus = "4.4.0"
addedcrates/polkit-shared/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/polkit-shared/src/lib.rs
@@ -0,0 +1,31 @@
+use std::collections::HashMap;
+
+use serde::{Deserialize, Serialize};
+use zbus::zvariant::{OwnedValue, Type};
+
+#[derive(Serialize, Deserialize, Type, PartialEq, Debug)]
+pub struct Identity {
+    pub kind: String,
+    pub details: HashMap<String, OwnedValue>,
+}
+
+impl Clone for Identity {
+    fn clone(&self) -> Self {
+        Self {
+            kind: self.kind.clone(),
+            details: self
+                .details
+                .iter()
+                .map(|(k, v)| (k.clone(), v.try_clone().expect("no fds are expected")))
+                .collect(),
+        }
+    }
+}
+
+#[derive(Serialize, Deserialize, Type, PartialEq, Debug)]
+pub struct BackendRequest {
+    pub cookie: String,
+    pub environment: HashMap<String, String>,
+    pub prompter_path: String,
+    pub identity: Identity,
+}
addedcrates/ui-prompt/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/crates/ui-prompt/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ui-prompt"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+serde = "1.0.204"
+thiserror = "1.0.63"
+tokio = { version = "1.39.2", features = ["io-util", "macros", "process", "rt"] }
+tracing = "0.1.40"
+zbus = { version = "4.4.0", optional = true }
+
+[features]
+default = ["dbus"]
+dbus = ["dep:zbus"]
addedcrates/ui-prompt/src/dbus.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/ui-prompt/src/dbus.rs
@@ -0,0 +1,123 @@
+use zbus::interface;
+use zbus::{fdo, proxy};
+
+use crate::Source;
+use crate::{BlockingPrompter, Result};
+use crate::{Error, Prompter};
+
+pub struct DbusPrompterInterface<P>(pub P);
+#[interface(name = "lach.PolkitInputHandler")]
+impl<P: Prompter + Send + Sync + 'static> DbusPrompterInterface<P> {
+    async fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: Vec<Source>,
+    ) -> fdo::Result<bool> {
+        Ok(self.0.prompt_radio(prompt, description, &source).await?)
+    }
+    async fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: Vec<Source>,
+    ) -> fdo::Result<String> {
+        Ok(self
+            .0
+            .prompt_text(echo, prompt, description, &source)
+            .await?)
+    }
+    async fn display_text(
+        &self,
+        error: bool,
+        description: &str,
+        source: Vec<Source>,
+    ) -> fdo::Result<()> {
+        Ok(self.0.display_text(error, description, &source).await?)
+    }
+}
+
+#[proxy(interface = "lach.PolkitInputHandler")]
+trait DbusPrompter {
+    async fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> fdo::Result<bool>;
+    async fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> fdo::Result<String>;
+    async fn display_text(
+        &self,
+        error: bool,
+        description: &str,
+        source: &[Source],
+    ) -> fdo::Result<()>;
+}
+
+impl Prompter for DbusPrompterProxy<'_> {
+    async fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<bool> {
+        Ok(self.prompt_radio(prompt, description, source).await?)
+    }
+
+    async fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<String> {
+        Ok(self.prompt_text(echo, prompt, description, source).await?)
+    }
+
+    async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+        Ok(self.display_text(error, description, source).await?)
+    }
+}
+impl BlockingPrompter for DbusPrompterProxyBlocking<'_> {
+    fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool> {
+        Ok(self.prompt_radio(prompt, description, source)?)
+    }
+
+    fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<String> {
+        Ok(self.prompt_text(echo, prompt, description, source)?)
+    }
+
+    fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+        Ok(self.display_text(error, description, source)?)
+    }
+}
+
+impl From<fdo::Error> for Error {
+    fn from(value: fdo::Error) -> Self {
+        if matches!(value, fdo::Error::NoReply(_)) {
+            return Self::Cancel;
+        }
+        Self::InputError(format!("{value}"))
+    }
+}
+impl From<Error> for fdo::Error {
+    fn from(value: Error) -> Self {
+        match value {
+            Error::Cancel => fdo::Error::NoReply("input was cancelled".to_owned()),
+            Error::InputError(e) => fdo::Error::Failed(e),
+        }
+    }
+}
addedcrates/ui-prompt/src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/ui-prompt/src/lib.rs
@@ -0,0 +1,104 @@
+use core::fmt;
+use std::borrow::Cow;
+use std::future::Future;
+use std::result;
+
+pub mod dbus;
+pub mod rofi;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("user has cancelled input")]
+    Cancel,
+    #[error("input error: {0}")]
+    InputError(String),
+}
+
+pub type Result<T, E = Error> = result::Result<T, E>;
+
+#[cfg_attr(feature = "dbus", derive(zbus::zvariant::Type))]
+#[derive(serde::Serialize, serde::Deserialize, Clone)]
+pub struct Source(Cow<'static, str>);
+impl fmt::Display for Source {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "<u>{}</u>", self.0)
+    }
+}
+
+pub trait Prompter {
+    fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> impl Future<Output = Result<bool>> + Send;
+    fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> impl Future<Output = Result<String>> + Send;
+    fn display_text(
+        &self,
+        error: bool,
+        description: &str,
+        source: &[Source],
+    ) -> impl Future<Output = Result<()>> + Send;
+}
+pub trait BlockingPrompter {
+    fn prompt_radio(&self, prompt: &str, description: &str, source: &[Source]) -> Result<bool>;
+    fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<String>;
+    fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()>;
+}
+
+pub struct PrependSourcePrompter<P> {
+    prompter: P,
+    source: Vec<Source>,
+}
+impl<P> PrependSourcePrompter<P> {
+    fn source(&self, input: &[Source]) -> Vec<Source> {
+        let mut out = self.source.clone();
+        out.extend(input.iter().cloned());
+        out
+    }
+}
+impl<P> Prompter for PrependSourcePrompter<P>
+where
+    P: Prompter + Sync,
+{
+    async fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<bool> {
+        self.prompter
+            .prompt_radio(prompt, description, &self.source(source))
+            .await
+    }
+
+    async fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<String> {
+        self.prompter
+            .prompt_text(echo, prompt, description, &self.source(source))
+            .await
+    }
+
+    async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+        self.prompter
+            .display_text(error, description, &self.source(source))
+            .await
+    }
+}
addedcrates/ui-prompt/src/rofi.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/ui-prompt/src/rofi.rs
@@ -0,0 +1,176 @@
+use std::process::Stdio;
+
+use tokio::io::AsyncWriteExt;
+use tokio::process::Command;
+use tracing::trace;
+
+use crate::{Error, Prompter, Result, Source};
+
+pub struct RofiPrompter;
+
+impl Prompter for RofiPrompter {
+    async fn prompt_radio(
+        &self,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<bool> {
+        trace!("rofi radio");
+        let mut cmd = Command::new("rofi");
+        let mesg = if source.is_empty() {
+            description.to_owned()
+        } else {
+            let mut out = format!("{description}\n\n<b>Requested on ",);
+            for s in source.iter() {
+                out.push_str(&s.to_string());
+            }
+            out.push_str("</b>");
+            out
+        };
+        cmd.args([
+            "-dmenu",
+            "-mesg",
+            &mesg,
+            "-sync",
+            "-only-match",
+            "-p",
+            prompt,
+        ]);
+        cmd.stdin(Stdio::piped());
+        cmd.stdout(Stdio::piped());
+        cmd.kill_on_drop(true);
+        let mut child = cmd
+            .spawn()
+            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+
+        child
+            .stdin
+            .take()
+            .expect("stdin is piped")
+            .write_all(b"Yes\nNo\n")
+            .await
+            .map_err(|e| Error::InputError(format!("failed to write rofi variants: {e}")))?;
+
+        let out = child
+            .wait_with_output()
+            .await
+            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+        let stdout = out
+            .stdout
+            .strip_suffix(b"\n")
+            .unwrap_or(&out.stdout)
+            .to_owned();
+
+        if &stdout == b"Yes" {
+            Ok(true)
+        } else if &stdout == b"No" {
+            Ok(false)
+        } else {
+            Err(Error::InputError("bad rofi response".to_owned()))
+        }
+    }
+
+    async fn prompt_text(
+        &self,
+        echo: bool,
+        prompt: &str,
+        description: &str,
+        source: &[Source],
+    ) -> Result<String> {
+        trace!("rofi text");
+        let mut cmd = Command::new("rofi");
+        let mesg = if source.is_empty() {
+            description.to_owned()
+        } else {
+            let mut out = format!("{description}\n\n<b>Requested on ",);
+            for s in source.iter() {
+                out.push_str(&s.to_string());
+            }
+            out.push_str("</b>");
+            out
+        };
+        cmd.args(["-dmenu", "-mesg", &mesg, "-p", prompt]);
+        if !echo {
+            cmd.arg("-password");
+        }
+        cmd.stdin(Stdio::null());
+        cmd.stdout(Stdio::piped());
+        cmd.kill_on_drop(true);
+        let child = cmd
+            .spawn()
+            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+
+        let out = child
+            .wait_with_output()
+            .await
+            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+        let stdout = out
+            .stdout
+            .strip_suffix(b"\n")
+            .unwrap_or(&out.stdout)
+            .to_owned();
+
+        Ok(String::from_utf8_lossy(&stdout).to_string())
+    }
+
+    async fn display_text(&self, error: bool, description: &str, source: &[Source]) -> Result<()> {
+        trace!("rofi display");
+        let mut cmd = Command::new("rofi");
+        let mut mesg = if source.is_empty() {
+            description.to_owned()
+        } else {
+            let mut out = format!("{description}\n\n<b>Coming from ",);
+            for s in source.iter() {
+                out.push_str(&s.to_string());
+            }
+            out.push_str("</b>");
+            out
+        };
+        if error {
+            mesg.insert_str(0, "<span color=\"red\">");
+            mesg.push_str("</span>");
+        }
+        cmd.args(["-e", &mesg, "-markup"]);
+        cmd.stdin(Stdio::null());
+        cmd.stdout(Stdio::null());
+        cmd.kill_on_drop(true);
+        let mut child = cmd
+            .spawn()
+            .map_err(|e| Error::InputError(format!("failed to spawn rofi: {e}")))?;
+
+        child
+            .wait()
+            .await
+            .map_err(|e| Error::InputError(format!("failed to wait for rofi: {e}")))?;
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::borrow::Cow;
+
+    use crate::rofi::RofiPrompter;
+    use crate::{PrependSourcePrompter, Prompter as _, Source};
+
+    #[tokio::test]
+    async fn test() {
+        let prompter = PrependSourcePrompter {
+            prompter: RofiPrompter,
+            source: vec![Source(Cow::Borrowed("ssh"))],
+        };
+        prompter
+            .prompt_radio("Enable", "Polkit needs access", &[])
+            .await
+            .expect("rofi");
+        prompter
+            .prompt_text(false, "Password", "Polkit needs access", &[])
+            .await
+            .expect("rofi");
+        prompter
+            .display_text(true, "Polkit needs access", &[])
+            .await
+            .expect("rofi");
+    }
+}
modifiedflake.lockdiffbeforeafterboth
--- a/flake.lock
+++ b/flake.lock
@@ -1,20 +1,42 @@
 {
   "nodes": {
-    "flake-utils": {
+    "crane": {
       "inputs": {
-        "systems": "systems"
+        "nixpkgs": [
+          "nixpkgs"
+        ]
       },
       "locked": {
-        "lastModified": 1710146030,
-        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
-        "owner": "numtide",
-        "repo": "flake-utils",
-        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+        "lastModified": 1721842668,
+        "narHash": "sha256-k3oiD2z2AAwBFLa4+xfU+7G5fisRXfkvrMTCJrjZzXo=",
+        "owner": "ipetkov",
+        "repo": "crane",
+        "rev": "529c1a0b1f29f0d78fa3086b8f6a134c71ef3aaf",
+        "type": "github"
+      },
+      "original": {
+        "owner": "ipetkov",
+        "repo": "crane",
+        "type": "github"
+      }
+    },
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1719994518,
+        "narHash": "sha256-pQMhCCHyQGRzdfAkdJ4cIWiw+JNuWsTX7f0ZYSyz0VY=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "9227223f6d922fee3c7b190b2cc238a99527bbb7",
         "type": "github"
       },
       "original": {
-        "owner": "numtide",
-        "repo": "flake-utils",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
         "type": "github"
       }
     },
@@ -35,9 +57,11 @@
     },
     "root": {
       "inputs": {
-        "flake-utils": "flake-utils",
+        "crane": "crane",
+        "flake-parts": "flake-parts",
         "nixpkgs": "nixpkgs",
-        "rust-overlay": "rust-overlay"
+        "rust-overlay": "rust-overlay",
+        "shelly": "shelly"
       }
     },
     "rust-overlay": {
@@ -60,18 +84,26 @@
         "type": "github"
       }
     },
-    "systems": {
+    "shelly": {
+      "inputs": {
+        "flake-parts": [
+          "flake-parts"
+        ],
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
       "locked": {
-        "lastModified": 1681028828,
-        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
-        "owner": "nix-systems",
-        "repo": "default",
-        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "lastModified": 1718420551,
+        "narHash": "sha256-NU8NBXVPj0KuY4Tl/LtZPrbX3PmmmgPuhk/1pzm9cyk=",
+        "owner": "CertainLach",
+        "repo": "shelly",
+        "rev": "4f70221f3f9ad9058f590eefb25251b6760aaa47",
         "type": "github"
       },
       "original": {
-        "owner": "nix-systems",
-        "repo": "default",
+        "owner": "CertainLach",
+        "repo": "shelly",
         "type": "github"
       }
     }
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -2,28 +2,55 @@
   description = "Jrsonnet";
   inputs = {
     nixpkgs.url = "github:nixos/nixpkgs";
-    flake-utils.url = "github:numtide/flake-utils";
+    flake-parts = {
+      url = "github:hercules-ci/flake-parts";
+      inputs.nixpkgs-lib.follows = "nixpkgs";
+    };
     rust-overlay = {
       url = "github:oxalica/rust-overlay";
       inputs.nixpkgs.follows = "nixpkgs";
-      inputs.flake-utils.follows = "flake-utils";
     };
+    crane = {
+      url = "github:ipetkov/crane";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    shelly = {
+      url = "github:CertainLach/shelly";
+      inputs = {
+        flake-parts.follows = "flake-parts";
+        nixpkgs.follows = "nixpkgs";
+      };
+    };
   };
-  outputs = { nixpkgs, flake-utils, rust-overlay, ... }:
-    flake-utils.lib.eachDefaultSystem (system:
-      let
-        pkgs = import nixpkgs {
+  outputs = inputs @ {
+    nixpkgs,
+    flake-parts,
+    rust-overlay,
+    crane,
+    ...
+  }:
+    flake-parts.lib.mkFlake {inherit inputs;} {
+      imports = [inputs.shelly.flakeModule ./nix/nixos-modules.nix];
+      systems = nixpkgs.lib.systems.flakeExposed;
+      perSystem = {
+        config,
+        system,
+        pkgs,
+        ...
+      }: let
+        rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
+        craneLib = (crane.mkLib pkgs).overrideToolchain rust;
+      in {
+        _module.args.pkgs = import nixpkgs {
           inherit system;
-          overlays = [ rust-overlay.overlays.default ];
+          overlays = [rust-overlay.overlays.default];
         };
-        rust = ((pkgs.rustChannelOf { date = "2024-07-26"; channel = "nightly"; }).default.override {
-          extensions = [ "rust-src" "miri" "rust-analyzer" ];
-        });
-      in
-      rec {
-        devShell = pkgs.mkShell {
-          nativeBuildInputs = with pkgs;[
-            rust
+        packages.polkit-backend = pkgs.callPackage ./nix/polkit-backend.nix {
+          inherit craneLib;
+        };
+        shelly.shells.default = {
+          factory = craneLib.devShell;
+          packages = with pkgs; [
             cargo-edit
             cargo-asm
             cargo-outdated
@@ -37,6 +64,7 @@
             pam
           ];
         };
-      }
-    );
+        formatter = pkgs.alejandra;
+      };
+    };
 }
addednix/nixos-modules.nixdiffbeforeafterboth
--- /dev/null
+++ b/nix/nixos-modules.nix
@@ -0,0 +1,13 @@
+{config, ...}: {
+  flake.nixosModules = rec {
+    default = polkit-backend;
+    polkit-backend = {pkgs, ...}: {
+      services.dbus.packages = [
+        config.flake.packages.${pkgs.stdenv.system}.polkit-backend
+      ];
+      systemd.packages = [
+        config.flake.packages.${pkgs.stdenv.system}.polkit-backend
+      ];
+    };
+  };
+}
addednix/polkit-backend.nixdiffbeforeafterboth
--- /dev/null
+++ b/nix/polkit-backend.nix
@@ -0,0 +1,40 @@
+{
+  lib,
+  craneLib,
+  rustPlatform,
+  pam,
+  coreutils,
+}: let
+  crateName = "polkit-backend";
+in
+  craneLib.buildPackage {
+    src = lib.cleanSourceWith {
+      src = ../.;
+      filter = path: type:
+        (lib.hasSuffix "\.conf" path)
+        || (lib.hasSuffix "\.service" path)
+        || (craneLib.filterCargoSources path type);
+    };
+
+    pname = crateName;
+    installPhase = ''
+      mkdir -p $out/libexec
+      cp -ar $src/cmds/${crateName}/share $out/
+      cp -ar $src/cmds/${crateName}/etc $out/
+      # Reference stripper hook wants write access to that.
+      chmod a+w -R $out/share
+      cp target/release/${crateName} $out/libexec/
+
+      substituteInPlace $out/share/dbus-1/system-services/lach.polkit.helper1.conf \
+        --replace-fail /bin/false ${coreutils}/bin/false
+      substituteInPlace $out/etc/systemd/system/remowt-polkit-helper.service \
+        --replace-fail @libexecdir@ ${placeholder "out"}/libexec
+    '';
+
+    cargoExtraArgs = "--locked -p ${crateName}";
+
+    buildInputs = [
+      rustPlatform.bindgenHook
+      pam
+    ];
+  }
addedrust-toolchain.tomldiffbeforeafterboth
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,3 @@
+[toolchain]
+channel = "nightly-2024-07-20"
+components = ["rustfmt", "clippy", "rust-analyzer", "rust-src"]
deletedsrc/main.rsdiffbeforeafterboth
--- a/src/main.rs
+++ /dev/null
@@ -1,154 +0,0 @@
-use std::collections::HashMap;
-use std::ffi::{CStr, CString};
-use std::future;
-
-use pam_client::{Context, ConversationHandler, ErrorCode, Flag};
-use serde::Deserialize;
-use zbus::zvariant::{OwnedValue, Str, Type, Value};
-use zbus::{interface, Connection};
-use zbus_polkit::policykit1::Subject;
-
-/// Helper struct that implements serde::Deserialize
-#[derive(Deserialize, Type, PartialEq, Debug)]
-pub struct Identity<'s> {
-    pub kind: &'s str,
-    pub details: HashMap<&'s str, Value<'s>>,
-}
-struct Agent {
-    // sessions: HashMap<String, Session>,
-}
-
-impl Agent {
-    pub fn new() -> Self {
-        Self {
-            // sessions: HashMap::new(),
-        }
-    }
-}
-#[interface(name = "org.freedesktop.PolicyKit1.AuthenticationAgent")]
-impl Agent {
-    /// BeginAuthentication method
-    async fn begin_authentication(
-        &mut self,
-        action_id: &str,
-        message: &str,
-        icon_name: &str,
-        details: HashMap<&str, &str>,
-        cookie: &str,
-        identities: Vec<Identity<'_>>,
-    ) -> zbus::fdo::Result<()> {
-        println!("ASKED");
-        dbg!(action_id, message, icon_name, details, cookie, identities);
-        // debug!("Authentication asked !");
-        // debug!("action_id: {action_id}, message: {message}, icon_name: {icon_name}, details: {details:?}, cookie: {cookie}, identities: {identities:?}");
-        //
-        // let identities = identities
-        //     .into_iter()
-        //     .map(TryFrom::try_from)
-        //     .collect::<Result<Vec<OwnedIdentity>, <OwnedIdentity as TryFrom<Identity<'_>>>::Error>>(
-        //     )
-        //     .unwrap();
-        // let session = Session {
-        //     action_id: action_id.to_string(),
-        //     message: message.to_string(),
-        //     icon_name: icon_name.to_string(),
-        //     cookie: cookie.to_string(),
-        //     identities,
-        //     selected_identity_index: 0,
-        // };
-        // let out = authenticate(&session);
-        //
-        // info!("Responder exit code: {}", out.unwrap());
-        //
-        // self.sessions.insert(String::from(cookie), session);
-        Ok(())
-    }
-
-    /// CancelAuthentication method
-    async fn cancel_authentication(&self, cookie: &str) -> zbus::fdo::Result<()> {
-        println!("CANCELED");
-        // debug!("Authentication cancled ! {cookie}");
-        Ok(())
-    }
-}
-
-const OBJ_PATH: &str = "/0lach/polkitAgent";
-// #[tokio::main]
-async fn mainw() -> anyhow::Result<()> {
-    let conn = Connection::system().await?;
-    let proxy = zbus_polkit::policykit1::AuthorityProxy::new(&conn).await?;
-    conn.object_server().at(OBJ_PATH, Agent::new()).await?;
-    // conn.
-
-    let session_id = std::env::var("XDG_SESSION_ID")?;
-    let mut details = HashMap::new();
-    let val: OwnedValue = {
-        let wrapped: Str<'_> = session_id.into();
-        wrapped.into()
-    };
-    details.insert("session-id".to_string(), val);
-    proxy
-        .register_authentication_agent(
-            &Subject {
-                subject_kind: "unix-session".to_string(),
-                subject_details: details,
-            },
-            "C",
-            OBJ_PATH,
-        )
-        .await?;
-
-    future::pending::<()>().await;
-    Ok(())
-}
-
-struct Conv;
-impl ConversationHandler for Conv {
-    #[doc = " Obtains a string whilst echoing text (e.g. username)"]
-    #[doc = ""]
-    #[doc = " # Errors"]
-    #[doc = " You should return one of the following error codes on failure."]
-    #[doc = " - [`ErrorCode::CONV_ERR`]: Conversation failure."]
-    #[doc = " - [`ErrorCode::BUF_ERR`]: Memory allocation error."]
-    #[doc = " - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should"]
-    #[doc = "   pass [`ErrorCode::INCOMPLETE`] to the application and let it"]
-    #[doc = "   try again later."]
-    fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
-        todo!()
-    }
-
-    #[doc = " Obtains a string without echoing any text (e.g. password)"]
-    #[doc = ""]
-    #[doc = " # Errors"]
-    #[doc = " You should return one of the following error codes on failure."]
-    #[doc = " - [`ErrorCode::CONV_ERR`]: Conversation failure."]
-    #[doc = " - [`ErrorCode::BUF_ERR`]: Memory allocation error."]
-    #[doc = " - [`ErrorCode::CONV_AGAIN`]: no result yet, the PAM library should"]
-    #[doc = "   pass [`ErrorCode::INCOMPLETE`] to the application and let it"]
-    #[doc = "   try again later."]
-    fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {
-        dbg!(prompt);
-        Ok(CString::new("1").expect("valid"))
-    }
-
-    #[doc = " Displays some text."]
-    fn text_info(&mut self, msg: &CStr) {
-        todo!()
-    }
-
-    #[doc = " Displays an error message."]
-    fn error_msg(&mut self, msg: &CStr) {
-        todo!()
-    }
-}
-
-#[tokio::main]
-async fn main() {
-    let mut ctx = Context::new("login", Some("lach"), Conv).expect("context init failed");
-
-
-    ctx.set_user_prompt(Some("Hello"));
-    ctx.authenticate(Flag::NONE).expect("fail?");
-
-    println!("authenticated!");
-}