Skip to main content

rustsmb/
security.rs

1//! NT security-descriptor helpers built on the `win-sd` crate ([MS-DTYP]
2//! §2.4.6). Marshalling of the self-relative `SECURITY_DESCRIPTOR` / `ACL` /
3//! `SID` blobs is delegated to `win-sd` rather than hand-rolled here.
4//!
5//! The POSIX backend cannot express NT ACLs natively, so descriptors are
6//! stored verbatim (see [`smb_server_vfs::Vfs::get_security`] /
7//! [`smb_server_vfs::Vfs::set_security`]). When a file has no stored descriptor a
8//! permissive default is synthesised so clients always see a valid
9//! owner/group/DACL.
10
11use win_sd::{AccessMask, SecurityDescriptor, SecurityDescriptorBuilder, Sid};
12
13/// SECURITY_INFORMATION component-selector bits ([MS-DTYP] §2.4.7).
14pub mod sec_info {
15    /// Owner SID.
16    pub const OWNER: u32 = 0x0000_0001;
17    /// Group SID.
18    pub const GROUP: u32 = 0x0000_0002;
19    /// Discretionary ACL.
20    pub const DACL: u32 = 0x0000_0004;
21    /// System ACL.
22    pub const SACL: u32 = 0x0000_0008;
23    /// Default selection when the client passes 0 (owner + group + DACL).
24    pub const DEFAULT: u32 = OWNER | GROUP | DACL;
25}
26
27/// Permissive default: owner/group `BUILTIN\Administrators`, DACL granting
28/// `Everyone` full control. Returned when a file has no stored descriptor.
29fn default_descriptor() -> SecurityDescriptor {
30    SecurityDescriptorBuilder::new()
31        .owner(Sid::administrators())
32        .group(Sid::administrators())
33        .allow(Sid::everyone(), AccessMask::FILE_ALL_ACCESS)
34        .build()
35}
36
37/// Self-relative descriptor bytes for a QUERY SECURITY, keeping only the
38/// components named in `additional`. `stored` is the backend's saved blob.
39pub fn query_security(stored: Option<&[u8]>, additional: u32) -> Option<Vec<u8>> {
40    let src = match stored {
41        Some(bytes) => SecurityDescriptor::from_bytes(bytes).unwrap_or_else(|_| default_descriptor()),
42        None => default_descriptor(),
43    };
44    let mut out = SecurityDescriptor::new();
45    if additional & sec_info::OWNER != 0
46        && let Some(o) = src.owner() {
47            out.set_owner(o.clone());
48        }
49    if additional & sec_info::GROUP != 0
50        && let Some(g) = src.group() {
51            out.set_group(g.clone());
52        }
53    if additional & sec_info::DACL != 0
54        && let Some(d) = src.dacl() {
55            out.set_dacl(d.clone());
56        }
57    out.to_bytes().ok()
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[test]
65    fn default_query_is_parseable_and_has_dacl() {
66        let bytes = query_security(None, sec_info::DEFAULT).expect("encode");
67        let sd = SecurityDescriptor::from_bytes(&bytes).expect("round-trip parse");
68        assert!(sd.owner().is_some(), "owner present");
69        assert!(sd.dacl().is_some(), "DACL present");
70    }
71
72    #[test]
73    fn dacl_only_omits_owner() {
74        let bytes = query_security(None, sec_info::DACL).expect("encode");
75        let sd = SecurityDescriptor::from_bytes(&bytes).expect("parse");
76        assert!(sd.owner().is_none(), "owner omitted when not requested");
77        assert!(sd.dacl().is_some(), "DACL still present");
78    }
79
80    #[test]
81    fn stored_descriptor_round_trips() {
82        let stored = SecurityDescriptorBuilder::new()
83            .owner(Sid::local_system())
84            .allow(Sid::everyone(), AccessMask::FILE_GENERIC_READ)
85            .build()
86            .to_bytes()
87            .unwrap();
88        let out = query_security(Some(&stored), sec_info::OWNER).expect("encode");
89        let sd = SecurityDescriptor::from_bytes(&out).expect("parse");
90        assert_eq!(sd.owner(), Some(&Sid::local_system()), "stored owner preserved");
91    }
92}