difftreelog
refactor more work on this project
in: trunk
24 files changed
Cargo.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"
Cargo.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"
cmds/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"] }
cmds/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<()>;
+}
cmds/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"] }
cmds/polkit-backend/etc/systemd/system/remowt-polkit-helper.servicediffbeforeafterboth--- /dev/null
+++ b/cmds/polkit-backend/etc/systemd/system/remowt-polkit-helper.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Remowt polkit helper service
+
+[Service]
+Type=dbus
+BusName=lach.polkit.helper1
+ExecStart=@libexecdir@/polkit-helper
+# TODO: Hardening
+
+[Install]
+WantedBy=multi-user.target
+Alias=dbus-lach.polkit.helper1.service
cmds/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
cmds/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>
cmds/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
+}
cmds/polkit-helper/Cargo.lockdiffbeforeafterbothno changes
cmds/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"] }
cmds/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
-}
crates/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"
crates/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,
+}
crates/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"]
crates/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),
+ }
+ }
+}
crates/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
+ }
+}
crates/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");
+ }
+}
flake.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"
}
}
flake.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;
+ };
+ };
}
nix/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
+ ];
+ };
+ };
+}
nix/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
+ ];
+ }
rust-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"]
src/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!");
-}