__  __    __   __  _____      _            _          _____ _          _ _ 
 |  \/  |   \ \ / / |  __ \    (_)          | |        / ____| |        | | |
 | \  / |_ __\ V /  | |__) | __ ___   ____ _| |_ ___  | (___ | |__   ___| | |
 | |\/| | '__|> <   |  ___/ '__| \ \ / / _` | __/ _ \  \___ \| '_ \ / _ \ | |
 | |  | | |_ / . \  | |   | |  | |\ V / (_| | ||  __/  ____) | | | |  __/ | |
 |_|  |_|_(_)_/ \_\ |_|   |_|  |_| \_/ \__,_|\__\___| |_____/|_| |_|\___V 2.1
 if you need WebShell for Seo everyday contact me on Telegram
 Telegram Address : @jackleet
        
        
For_More_Tools: Telegram: @jackleet | Bulk Smtp support mail sender | Business Mail Collector | Mail Bouncer All Mail | Bulk Office Mail Validator | Html Letter private



Upload:

Command:

[email protected]: ~ $
use crate::{
    snapd_client::{prompt::RawPrompt, response::parse_response},
    socket_client::UnixSocketClient,
    Error, Result,
};
use chrono::{DateTime, SecondsFormat, Utc};
use hyper::Uri;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{collections::HashMap, env, str::FromStr};
use tokio::net::UnixStream;
use tracing::{debug, error, info, warn};

pub mod interfaces;
mod prompt;
mod response;

pub use interfaces::{TypedPrompt, TypedPromptReply, TypedUiInput};
pub use prompt::{Action, Lifespan, Prompt, PromptReply, UiInput};
pub use response::{RuleConflict, SnapdError};

const FEATURE_NAME: &str = "apparmor-prompting";
const LONG_POLL_TIMEOUT: &str = "1h";
const NOTICE_TYPES: &str = "interfaces-requests-prompt";
const SNAPD_BASE_URI: &str = "http://localhost/v2";
const SNAPD_SOCKET: &str = "/run/snapd.socket";
const SNAPD_SNAP_SOCKET: &str = "/run/snapd-snap.socket";
const SNAPD_ABSTRACT_SNAP_SOCKET: &str = "\0/snapd/snapd-snap.socket";

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct PromptId(pub String);

#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub enum PromptNotice {
    Update(PromptId),
    Resolved(PromptId),
}

/// Abstraction layer to make swapping out the underlying client possible for
/// testing.
#[allow(async_fn_in_trait)]
pub trait Client {
    async fn get_json<T>(&self, path: &str) -> Result<T>
    where
        T: DeserializeOwned;

    async fn post_json<T, U>(&self, path: &str, body: U) -> Result<T>
    where
        T: DeserializeOwned,
        U: Serialize;
}

impl Client for UnixSocketClient {
    async fn get_json<T>(&self, path: &str) -> Result<T>
    where
        T: DeserializeOwned,
    {
        let s = format!("{SNAPD_BASE_URI}/{path}");
        let uri = Uri::from_str(&s).map_err(|_| Error::InvalidUri {
            reason: "malformed",
            uri: s,
        })?;

        let res = self.get(uri).await?;

        parse_response(res).await
    }

    async fn post_json<T, U>(&self, path: &str, body: U) -> Result<T>
    where
        T: DeserializeOwned,
        U: Serialize,
    {
        let s = format!("{SNAPD_BASE_URI}/{path}");
        let uri = Uri::from_str(&s).map_err(|_| Error::InvalidUri {
            reason: "malformed",
            uri: s,
        })?;

        let res = self
            .post(uri, "application/json", serde_json::to_vec(&body)?)
            .await?;

        parse_response(res).await
    }
}

#[derive(Debug, Clone)]
pub struct SnapdClient<C>
where
    C: Client,
{
    client: C,
    notices_after: String,
}

pub type SnapdSocketClient = SnapdClient<UnixSocketClient>;

impl SnapdSocketClient {
    pub async fn new() -> Self {
        Self::new_with_notices_after(Utc::now()).await
    }

    pub async fn new_with_notices_after(dt: DateTime<Utc>) -> Self {
        let socket = if env::var("SNAP_NAME").is_ok() {
            if UnixStream::connect(SNAPD_ABSTRACT_SNAP_SOCKET)
                .await
                .is_ok()
            {
                info!("using the abstract snapd snap socket at address: @{SNAPD_ABSTRACT_SNAP_SOCKET}");
                SNAPD_ABSTRACT_SNAP_SOCKET
            } else {
                info!("using the snapd snap socket at address: {SNAPD_SNAP_SOCKET}");
                SNAPD_SNAP_SOCKET
            }
        } else {
            info!("using the snapd socket at address: {SNAPD_SOCKET}");
            SNAPD_SOCKET
        };

        Self {
            client: UnixSocketClient::new(socket),
            notices_after: dt.to_rfc3339_opts(SecondsFormat::Nanos, true),
        }
    }
}

impl<C> SnapdClient<C>
where
    C: Client,
{
    /// Check whether or not the apparmor-prompting feature is enabled on this system
    pub async fn is_prompting_enabled(&self) -> Result<bool> {
        let info: SysInfo = self.client.get_json("system-info").await?;

        info.prompting_enabled()
    }

    /// If prompting is not currently enabled then we exit non-0 to ensure that systemd does not
    /// restart us. Instead, snapd will ensure that we are started when the flag is enabled.
    pub async fn exit_if_prompting_not_enabled(&self) -> Result<()> {
        if !self.is_prompting_enabled().await? {
            warn!("the prompting feature is not enabled: exiting");
            return Err(Error::NotEnabled);
        }

        Ok(())
    }

    /// HTTP long poll on the /v2/notices API from snapd to await prompt requests for the user we
    /// are running under.
    ///
    /// Calling this method will update our [Self::notices_after] field when we successfully obtain
    /// new notices from snapd.
    ///
    /// Notices from snapd have an optional top level key of 'last-data' which can contain
    /// metadata that allows us to filter what IDs we need to look at. If the 'resolved' key is
    /// present and if its value is 'replied' then this is snapd telling us that a prompt has
    /// been actioned and we should clear any internal state we have associated with that ID.
    pub async fn pending_prompt_notices(&mut self) -> Result<Vec<PromptNotice>> {
        let path = format!(
            "notices?types={NOTICE_TYPES}&timeout={LONG_POLL_TIMEOUT}&after={}",
            self.notices_after
        );

        let raw_notices: Vec<Notice> = self.client.get_json(&path).await?;
        if let Some(n) = raw_notices.last() {
            n.last_occurred.clone_into(&mut self.notices_after);
        }

        debug!("received notices: {raw_notices:?}");

        let notices: Vec<PromptNotice> = raw_notices
            .into_iter()
            .map(|n| match n.last_data {
                Some(LastData { resolved: Some(s) }) if s == "replied" => {
                    PromptNotice::Resolved(n.key)
                }
                _ => PromptNotice::Update(n.key),
            })
            .collect();

        return Ok(notices);

        // serde structs

        #[derive(Debug, Deserialize)]
        #[serde(rename_all = "kebab-case")]
        struct Notice {
            key: PromptId,
            last_occurred: String,
            #[serde(default)]
            last_data: Option<LastData>,
            #[allow(dead_code)]
            #[serde(flatten)]
            extra: HashMap<String, serde_json::Value>,
        }

        #[derive(Debug, Deserialize)]
        #[serde(rename_all = "kebab-case")]
        struct LastData {
            #[serde(default)]
            resolved: Option<String>,
        }
    }

    /// Pull details for all pending prompts from snapd
    pub async fn all_pending_prompt_details(&self) -> Result<Vec<TypedPrompt>> {
        let raw_prompts: Vec<RawPrompt> =
            self.client.get_json("interfaces/requests/prompts").await?;

        raw_prompts.into_iter().map(|p| p.try_into()).collect()
    }

    /// Pull details for a specific prompt from snapd
    pub async fn prompt_details(&self, id: &PromptId) -> Result<TypedPrompt> {
        let prompt: RawPrompt = self
            .client
            .get_json(&format!("interfaces/requests/prompts/{}", id.0))
            .await?;

        prompt.try_into()
    }

    /// Submit a reply to the given prompt to snapd
    pub async fn reply_to_prompt(
        &self,
        id: &PromptId,
        reply: TypedPromptReply,
    ) -> Result<Vec<PromptId>> {
        let resp: Option<Vec<PromptId>> = self
            .client
            .post_json(&format!("interfaces/requests/prompts/{}", id.0), reply)
            .await?;

        debug!(prompt = id.0, ?resp, "response from snapd");

        Ok(resp.unwrap_or_default())
    }

    /// Pull metadata for rendering apparmor prompts using the `snaps` snapd endpoint.
    pub async fn snap_metadata(&self, name: &str) -> Option<SnapMeta> {
        let res = self.client.get_json(&format!("snaps/{name}")).await;
        return match res {
            Ok(SnapDetails {
                install_date,
                publisher,
            }) => Some(SnapMeta {
                name: name.to_owned(),
                updated_at: install_date
                    .split_once('T')
                    .map(|(s, _)| s.to_owned())
                    .unwrap_or(install_date),
                store_url: format!("snap://{name}"),
                publisher: publisher.display_name,
            }),

            Err(e) => {
                error!("unable to pull snap metadata for {name}: {e}");
                None
            }
        };

        // Serde structs

        #[derive(Debug, Default, Deserialize)]
        #[serde(rename_all = "kebab-case")]
        struct SnapDetails {
            install_date: String,
            publisher: Publisher,
        }

        #[derive(Debug, Default, Deserialize)]
        #[serde(rename_all = "kebab-case")]
        struct Publisher {
            display_name: String,
        }
    }
}

#[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "kebab-case")]
pub struct SnapMeta {
    pub name: String,
    pub updated_at: String,
    pub store_url: String,
    pub publisher: String,
}

#[derive(Debug, Default, Deserialize)]
struct SysInfo {
    features: HashMap<String, Feature>,
}

impl SysInfo {
    fn prompting_enabled(mut self) -> Result<bool> {
        let f = self
            .features
            .remove(FEATURE_NAME)
            .ok_or(Error::NotAvailable)?;

        match f.unsupported_reason {
            Some(reason) => Err(Error::NotSupported { reason }),
            None => Ok(f.supported && f.enabled),
        }
    }
}

#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct Feature {
    enabled: bool,
    supported: bool,
    unsupported_reason: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;
    use simple_test_case::test_case;

    #[derive(Debug)]
    enum Enabled {
        Ok(bool),
        NotSupported(&'static str),
    }

    #[test_case(false, None, Enabled::Ok(false); "supported but not enabled")]
    #[test_case(true, None, Enabled::Ok(true); "supported and enabled")]
    #[test_case(false, Some("foo"), Enabled::NotSupported("foo"); "unsupported and not enabled")]
    #[test_case(true, Some("foo"), Enabled::NotSupported("foo"); "unsupported but enabled")]
    #[test]
    fn prompting_enabled_works(enabled: bool, unsupported_reason: Option<&str>, expected: Enabled) {
        let mut features = HashMap::default();
        features.insert(
            FEATURE_NAME.to_string(),
            Feature {
                enabled,
                supported: unsupported_reason.is_none(),
                unsupported_reason: unsupported_reason.map(String::from),
            },
        );

        let s = SysInfo { features };

        match (s.prompting_enabled(), expected) {
            (Ok(got), Enabled::Ok(wanted)) => assert_eq!(got, wanted, "boolean return was wrong"),
            (Err(Error::NotSupported { reason }), Enabled::NotSupported(s)) => {
                assert_eq!(reason, s)
            }
            (got, wanted) => panic!("expected {wanted:?}, got {got:?}"),
        }
    }

    #[test]
    fn prompting_enabled_errors_correctly_when_not_available() {
        let s = SysInfo::default();

        match s.prompting_enabled() {
            Err(Error::NotAvailable) => (),
            res => panic!("expected NotAvailable, got {res:?}"),
        }
    }
}

Filemanager

Name Type Size Permission Actions
interfaces Folder 0755
mod.rs File 11.08 KB 0644
prompt.rs File 5.96 KB 0644
response.rs File 12.26 KB 0644
Filemanager