--- a/cmds/polkit-dbus-helper/Cargo.toml +++ b/cmds/polkit-dbus-helper/Cargo.toml @@ -4,14 +4,14 @@ 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"] } +anyhow.workspace = true +clap = { workspace = true, features = ["derive"] } +nix.workspace = true +pam-client.workspace = true +polkit-shared.workspace = true +tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread"] } +tracing.workspace = true +tracing-subscriber.workspace = true +ui-prompt.workspace = true +zbus = { workspace = true, features = ["tokio"] } +zbus_polkit = { workspace = true, features = ["tokio"] } --- a/cmds/polkit-dbus-helper/src/main.rs +++ b/cmds/polkit-dbus-helper/src/main.rs @@ -18,219 +18,219 @@ use zbus::{blocking, interface, proxy, Connection}; struct Helper { - connection: Connection, - blocking_connection: blocking::Connection, + connection: Connection, + blocking_connection: blocking::Connection, } static ALLOWED_ENVIRONMENT: LazyLock> = 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() + [ + // 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); impl Conversation

{ - fn prompt_inner(&self, echo: bool, prompt: &CStr) -> Result { - trace!("do prompt"); - let out = self - .0 - .prompt_text(echo, &prompt.to_string_lossy(), "PAM 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, &[]); - } + fn prompt_inner(&self, echo: bool, prompt: &CStr) -> Result { + trace!("do prompt"); + let out = self + .0 + .prompt_text(echo, &prompt.to_string_lossy(), "PAM 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 ConversationHandler for Conversation

{ - fn prompt_echo_on(&mut self, prompt: &CStr) -> Result { - self.prompt_inner(true, prompt) - } + fn prompt_echo_on(&mut self, prompt: &CStr) -> Result { + self.prompt_inner(true, prompt) + } - fn prompt_echo_off(&mut self, prompt: &CStr) -> Result { - self.prompt_inner(false, prompt) - } + fn prompt_echo_off(&mut self, prompt: &CStr) -> Result { + self.prompt_inner(false, prompt) + } - fn text_info(&mut self, msg: &CStr) { - self.text_inner(false, msg) - } + 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 error_msg(&mut self, msg: &CStr) { + self.text_inner(true, msg) + } - fn radio_prompt(&mut self, prompt: &CStr) -> Result { - let prompt = prompt.to_string_lossy(); - let result = self - .0 - .prompt_radio(&prompt, "PAM prompt request", &[]) - .map_err(|_| ErrorCode::CONV_ERR)?; - Ok(result) - } + fn radio_prompt(&mut self, prompt: &CStr) -> Result { + let prompt = prompt.to_string_lossy(); + let result = self + .0 + .prompt_radio(&prompt, "PAM prompt request", &[]) + .map_err(|_| ErrorCode::CONV_ERR)?; + Ok(result) + } } #[proxy( - default_service = "org.freedesktop.DBus", - default_path = "/org/freedesktop/DBus" + default_service = "org.freedesktop.DBus", + default_path = "/org/freedesktop/DBus" )] trait DBus { - fn get_connection_credentials(&self, body: &str) -> zbus::Result>; + fn get_connection_credentials(&self, body: &str) -> zbus::Result>; } #[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())); - }; + 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?; + 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 connection_uid: u32 = (&reply["UnixUserID"]).try_into().unwrap(); + // 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 connection_uid: u32 = (&reply["UnixUserID"]).try_into().unwrap(); - let identity = request.identity.clone(); - let blocking_connection = self.blocking_connection.clone(); - let thread_result: fdo::Result<()> = block_in_place(move || { - trace!("find user"); - let Some(identity_uid) = identity.uid() else { - return Err(fdo::Error::AuthFailed("can't process identity".to_owned())); - }; - let user = User::from_uid(identity_uid) - .map_err(|_| fdo::Error::AuthFailed("error querying user".to_owned()))? - .ok_or_else(|| fdo::Error::AuthFailed("uid not found".to_owned()))?; + let identity = request.identity.clone(); + let blocking_connection = self.blocking_connection.clone(); + let thread_result: fdo::Result<()> = block_in_place(move || { + trace!("find user"); + let Some(identity_uid) = identity.uid() else { + return Err(fdo::Error::AuthFailed("can't process identity".to_owned())); + }; + let user = User::from_uid(identity_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()))?; + 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!("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!("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()))?; + trace!("acct mgmt"); + ctx.acct_mgmt(Flag::NONE) + .map_err(|_| fdo::Error::AuthFailed("pam acct mgmt failed".to_owned()))?; - Ok(()) - }); + Ok(()) + }); - thread_result?; + thread_result?; - trace!("respond"); - let proxy = zbus_polkit::policykit1::AuthorityProxy::new(&self.connection).await?; + 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::>(); - proxy - .authentication_agent_response2( - connection_uid, - &request.cookie, - &zbus_polkit::policykit1::Identity { - identity_kind: &request.identity.kind, - identity_details: &identity_details, - }, - ) - .await?; - Ok(()) - } + let identity_details = request + .identity + .details + .iter() + .map(|(k, v)| (k.as_str(), (**v).try_clone().expect("success"))) + .collect::>(); + proxy + .authentication_agent_response2( + connection_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, + /// 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")?; + 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 = 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")?; + let session = opts.session; + let blocking_connection: anyhow::Result = 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")?; - } + 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 + .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")?; + connection + .request_name("lach.polkit.helper1") + .await + .context("failed to request name")?; - pending().await + pending().await }