sddl 0.1.3

a library to parse and analyse SDDL Strings
Documentation
use std::fmt::Display;

use crate::*;
use binrw::binrw;
use derivative::Derivative;
use getset::Getters;
use serde::Serialize;

mod r#type;
pub use r#type::*;

mod revision;
pub use revision::*;

pub const ACL_HEADER_SIZE: u16 = 1 + 1 + 2 + 2 + 2;

/// The ACL structure is the header of an access control list (ACL). A complete
/// ACL consists of an ACL structure followed by an ordered list of zero or more
/// access control entries (ACEs).
///
/// <https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-acl>
#[binrw]
#[derive(Derivative, Getters, Debug, Clone, Serialize)]
#[derivative(Eq, PartialEq)]
#[getset(get = "pub")]
#[brw(little,import(control_flags: ControlFlags, acl_type: Type))]
pub struct Acl {
    /// Specifies the revision level of the ACL. This value should be
    /// ACL_REVISION, unless the ACL contains an object-specific ACE, in which
    /// case this value must be ACL_REVISION_DS. All ACEs in an ACL must be at
    /// the same revision level.
    acl_revision: Revision,

    /// Specifies a zero byte of padding that aligns the AclRevision member on a
    /// 16-bit boundary.
    #[br(temp)]
    #[bw(calc(0))]
    #[getset(skip)]
    #[serde(skip)]
    _sbz1: u8,

    /// Specifies the size, in bytes, of the ACL. This value includes the ACL
    /// structure, all the ACEs, and the potential unused memory.
    #[serde(skip)]
    acl_size: u16,

    /// Specifies the number of ACEs stored in the ACL.
    #[serde(skip)]
    ace_count: u16,

    /// Specifies two zero-bytes of padding that align the ACL structure on a
    /// 32-bit boundary.
    #[br(temp)]
    #[bw(calc(0))]
    #[getset(skip)]
    #[serde(skip)]
    _sbz2: u16,

    #[br(count=ace_count)]
    ace_list: Vec<Ace>,

    #[br(calc=acl_type)]
    #[bw(ignore)]
    #[serde(skip)]
    acl_type: Type,

    #[br(calc=control_flags)]
    #[bw(ignore)]
    #[serde(skip)]
    // this flag field might contain information about the whole security
    // descriptor, which might differ from the SD where the other
    // ACL came from. So, ACLs which are equal can be part of SDs with
    // different control flags. This is the reason we ignore this pseudo-field
    // here
    #[derivative(PartialEq = "ignore")]
    control_flags: ControlFlags,
}

impl Display for Acl {
    /// <https://learn.microsoft.com/de-de/windows/win32/secauthz/security-descriptor-string-format>
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        for ace in self.ace_list() {
            SDDL_ACE_BEGIN.fmt(f)?;
            ace.fmt(f)?;
            SDDL_ACE_END.fmt(f)?;
        }
        Ok(())
    }
}

impl Acl {
    pub fn new(
        acl_revision: Revision,
        acl_type: Type,
        control_flags: ControlFlags,
        ace_list: Vec<Ace>,
    ) -> Self {
        let acl_size = ACL_HEADER_SIZE + ace_list.iter().map(|ace| ace.raw_size()).sum::<u16>();
        let ace_count = ace_list.len().try_into().unwrap();
        Self {
            acl_revision,
            acl_size,
            ace_count,
            ace_list,
            acl_type,
            control_flags,
        }
    }

    /// parses an SDDL string
    ///
    /// # Example
    /// ```rust
    /// use sddl::*;
    /// let acl = Acl::from_sddl("D:P(A;CIOI;GRGX;;;BU)(A;CIOI;GA;;;BA)(A;CIOI;GA;;;SY)(A;CIOI;GA;;;CO)", None).unwrap();
    ///
    /// assert_eq!(*acl.acl_type(), acl::Type::DACL);
    /// assert_eq!(*acl.ace_count(), 4);
    /// ```
    pub fn from_sddl(value: &str, domain_rid: Option<&[u32]>) -> Result<Self, Error> {
        Ok(crate::security_descriptor::parser::AclParser::new().parse(domain_rid, value)?)
    }

    pub fn sddl_string(&self) -> String {
        let ace_list = self
            .ace_list()
            .iter()
            .map(|ace: &Ace| format!("{SDDL_ACE_BEGIN}{ace}{SDDL_ACE_END}"))
            .fold(String::new(), |a, b| a + &b);

        let acl_type = self.acl_type().sddl_string();
        let flags = self.control_flags().sddl_string(*self.acl_type());
        format!("{acl_type}{SDDL_DELIMINATOR}{flags}{ace_list}")
    }
}

/*
#[cfg(test)]
mod tests {
    use super::Acl;

    #[test]
    fn test_minimal_sacl() {
        let _acl = Acl::try_from("S:(;;;)").unwrap();
    }
}
     */