git.delta.rocks / remowt / refs/commits / a05ea003b8ee

difftreelog

source

cmds/polkit-helper/src/main.rs5.9 KiBsourcehistory
1use std::collections::{HashMap, HashSet};2use std::ffi::{CStr, CString};3use std::future::pending;4use std::sync::LazyLock;56use anyhow::Context as _;7use clap::Parser;8use nix::unistd::{setuid, Uid, User};9use pam_client::{Context, ConversationHandler, ErrorCode};10use tokio::task::spawn_blocking;11use zbus::blocking;12use zbus::message::Header;13use zbus::zvariant::OwnedValue;14use zbus::{15    blocking::Connection as BlockingConnection,16    fdo::{Error as FdoError, Result as FdoResult},17    interface, proxy, Connection,18};1920struct Helper {21    connection: Connection,22    blocking_connection: BlockingConnection,23}2425static ALLOWED_ENVIRONMENT: LazyLock<HashSet<&str>> = LazyLock::new(|| {26    [27        // pam ssh agent auth28        "SSH_AUTH_SOCK",29        // ssh itself provides this when running PAM30        "SSH_AUTH_INFO_0",31        // contains user which ran sudo32        "SUDO_USER",33    ]34    .into_iter()35    .collect()36});3738struct Conversation {39    responder: PolkitResponderProxyBlocking<'static>,40}41impl Conversation {42    fn prompt_inner(&self, echo: bool, prompt: &CStr) -> Result<CString, ErrorCode> {43        let out = self44            .responder45            .prompt(echo, &prompt.to_string_lossy())46            .map_err(|_| ErrorCode::CONV_ERR)?;47        Ok(CString::new(out).map_err(|_| ErrorCode::CONV_AGAIN)?)48    }49    fn text_inner(&self, error: bool, msg: &CStr) {50        let msg = msg.to_string_lossy();51        let _ = self.responder.text(error, &msg);52    }53}54impl ConversationHandler for Conversation {55    fn prompt_echo_on(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {56        self.prompt_inner(true, prompt)57    }5859    fn prompt_echo_off(&mut self, prompt: &CStr) -> Result<CString, ErrorCode> {60        self.prompt_inner(false, prompt)61    }6263    fn text_info(&mut self, msg: &CStr) {64        self.text_inner(false, msg)65    }6667    fn error_msg(&mut self, msg: &CStr) {68        self.text_inner(true, msg)69    }7071    fn radio_prompt(&mut self, prompt: &CStr) -> Result<bool, ErrorCode> {72        let prompt = prompt.to_string_lossy();73        let result = self74            .responder75            .radio(&prompt)76            .map_err(|_| ErrorCode::CONV_ERR)?;77        Ok(result)78    }79}8081#[proxy(82    default_service = "org.freedesktop.DBus",83    default_path = "/org/freedesktop/DBus"84)]85trait DBus {86    fn get_connection_credentials(&self, body: &str) -> zbus::Result<HashMap<String, OwnedValue>>;87}8889#[proxy(default_path = "/lach/PolkitResponder")]90trait PolkitResponder {91    fn prompt(&self, echo: bool, prompt: &str) -> zbus::Result<String>;92    fn text(&self, error: bool, msg: &str) -> zbus::Result<()>;93    fn radio(&self, msg: &str) -> zbus::Result<bool>;94}9596#[interface(name = "lach.PolkitHelper")]97impl Helper {98    async fn init_conversation(99        &self,100        environment: HashMap<String, String>,101        #[zbus(header)] hdr: Header<'_>,102    ) -> zbus::fdo::Result<()> {103        let Some(sender) = hdr.sender().map(|v| v.to_owned()) else {104            return Err(zbus::fdo::Error::AuthFailed("missing sender".to_owned()));105        };106107        let dbus = DBusProxy::new(&self.connection).await?;108109        let reply = dbus110            .get_connection_credentials("org.freedesktop.DBus")111            .await?;112        let uid: u32 = (&reply["UnixUserID"]).try_into().unwrap();113114        let blocking_connection = self.blocking_connection.clone();115        let thread_result: FdoResult<()> = spawn_blocking(move || {116            let user = User::from_uid(Uid::from_raw(uid))117                .map_err(|_| zbus::fdo::Error::AuthFailed("error querying user".to_owned()))?118                .ok_or_else(|| zbus::fdo::Error::AuthFailed("uid not found".to_owned()))?;119120            let responder = PolkitResponderProxyBlocking::new(&blocking_connection, sender)?;121            let conversation = Conversation { responder };122            let mut ctx = Context::new(123                // TODO: Should another scope be used?124                "login",125                Some(&user.name),126                conversation,127            )128            .map_err(|_| FdoError::Failed("pam context init failed".to_owned()))?;129130            for (k, v) in environment {131                if k.contains('=') || !ALLOWED_ENVIRONMENT.contains(k.as_str()) {132                    continue;133                }134                let _ = ctx.putenv(format!("{k}={v}"));135            }136137            Ok(())138        })139        .await140        .map_err(|_| FdoError::Failed("thread spawn failed".to_owned()))?;141142        thread_result?;143        // Context::new(hdr.)144        //145        Ok(())146    }147}148149const OBJ_PATH: &str = "/lach/polkitHelper";150151#[derive(Parser)]152struct Opts {153    /// Not recommended: start as a session connection, then use escalation154    /// to respond to polkit requests.155    #[arg(long)]156    session: bool,157}158159#[tokio::main(flavor = "current_thread")]160async fn main() -> anyhow::Result<()> {161    let opts = Opts::parse();162    let connection = if opts.session {163        Connection::session().await164    } else {165        Connection::system().await166    }167    .context("failed to open connection")?;168169    let session = opts.session;170    let blocking_connection: anyhow::Result<BlockingConnection> = spawn_blocking(move || {171        Ok(if session {172            BlockingConnection::session()?173        } else {174            BlockingConnection::system()?175        })176    })177    .await?;178    let blocking_connection = blocking_connection.context("failed to open blocking connection")?;179180    if opts.session {181        setuid(Uid::from_raw(0))182            .context("polkit-helper needs to be suid if run in session mode")?;183    }184185    connection186        .object_server()187        .at(188            OBJ_PATH,189            Helper {190                connection: connection.clone(),191                blocking_connection,192            },193        )194        .await195        .context("failed listen path")?;196197    connection198        .request_name("lach.PolkitHelper")199        .await200        .context("failed to request name")?;201202    pending().await203}