istamon 0.1.0

Simple Desktop application and cli to display the service and host states of an Icinga instance.
use crate::cfg::PasswordManager;
use zbus::Connection;

pub struct KWalletClient {
    connection: Connection,
    handle: i32,
    destination: Option<&'static str>,
    path: &'static str,
    iface: Option<&'static str>,
    app_name: &'static str,
    folder: &'static str,
}

pub fn open() -> zbus::Result<KWalletClient> {
    let destination = Some("org.kde.kwalletd5");
    let path = "/modules/kwalletd5";
    let iface = Some("org.kde.KWallet");
    let app_name = "de.lu-fennell.icinga-client";
    let folder = app_name;

    let c = Connection::new_session()?;
    let handle: i32 = c
        .call_method(
            destination,
            path,
            iface,
            "open",
            &("kdewallet", 0i64, app_name),
        )?
        .body()?;

    Ok(KWalletClient {
        connection: c,
        handle,
        destination,
        path,
        iface,
        app_name,
        folder,
    })
}

impl KWalletClient {
    fn close(&self) -> zbus::Result<()> {
        self.connection.call_method(
            self.destination,
            self.path,
            self.iface,
            "close",
            &(self.handle, false, self.app_name),
        )?;
        Ok(())
    }

    pub fn create_folder(&self) -> zbus::Result<()> {
        self.connection.call_method(
            self.destination,
            self.path,
            self.iface,
            "createFolder",
            &(self.handle, self.folder, self.app_name),
        )?;
        Ok(())
    }

    pub fn write_password(&self, key: &str, password: &str) -> zbus::Result<()> {
        self.connection.call_method(
            self.destination,
            self.path,
            self.iface,
            "writePassword",
            &(self.handle, self.folder, key, password, self.app_name),
        )?;
        Ok(())
    }

    pub fn read_password(&self, key: &str) -> zbus::Result<String> {
        let password = self
            .connection
            .call_method(
                self.destination,
                self.path,
                self.iface,
                "readPassword",
                &(self.handle, self.folder, key, self.app_name),
            )?
            .body()?;
        Ok(password)
    }

    pub fn remove_entry(&self, key: &str) -> zbus::Result<()> {
        self.connection.call_method(
            self.destination,
            self.path,
            self.iface,
            "removeEntry",
            &(self.handle, self.folder, key, self.app_name),
        )?;
        Ok(())
    }

    pub fn has_entry(&self, key: &str) -> zbus::Result<bool> {
        let result: bool = self
            .connection
            .call_method(
                self.destination,
                self.path,
                self.iface,
                "hasEntry",
                &(self.handle, self.folder, key, self.app_name),
            )?
            .body()?;
        Ok(result)
    }
}

impl Drop for KWalletClient {
    fn drop(&mut self) {
        if let Err(e) = self.close() {
            eprintln!("Failed to close kwallet: {}", e);
        }
    }
}

impl PasswordManager for KWalletClient {
    fn load_password(
        &self,
        host: &str,
        user: &str,
    ) -> Result<Option<String>, Box<dyn std::error::Error>> {
        let key = key(host, user);
        let has_entry = self.has_entry(&key)?;
        if has_entry {
            let password = self.read_password(&key)?;
            Ok(Some(password))
        } else {
            Ok(None)
        }
    }

    fn name(&self) -> String {
        "kwallet".to_string()
    }

    fn store_password(
        &self,
        host: &str,
        user: &str,
        password: &str,
    ) -> Result<(), Box<dyn std::error::Error>> {
        Ok(self.write_password(&key(host, user), password)?)
    }
}

fn key(host: &str, user: &str) -> String {
    format!("{}@{}", user, host)
}