smb_msg/
tree_connect.rs

1use binrw::prelude::*;
2use binrw::{NullWideString, io::TakeSeekExt};
3use modular_bitfield::prelude::*;
4use smb_dtyp::{
5    binrw_util::prelude::*,
6    security::{ACL, ClaimSecurityAttributeRelativeV1, SID},
7};
8
9#[bitfield]
10#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
11#[bw(map = |&x| Self::into_bytes(x))]
12#[br(map = Self::from_bytes)]
13pub struct TreeConnectRequestFlags {
14    pub cluster_reconnect: bool,
15    pub redirect_to_owner: bool,
16    pub extension_present: bool,
17    #[skip]
18    __: B13,
19}
20
21/// Tree Connect Request
22///
23/// Supports both the base and extension variants.
24/// - On read, uses extension iff `flags.extension_present()` - parses just like the server intends.
25/// - On write, uses extension iff `tree_connect_contexts` is non-empty.
26#[binrw::binrw]
27#[derive(Debug)]
28pub struct TreeConnectRequest {
29    #[bw(calc = 9)]
30    #[br(assert(_structure_size == 9))]
31    _structure_size: u16,
32    pub flags: TreeConnectRequestFlags,
33    #[bw(calc = PosMarker::default())]
34    _path_offset: PosMarker<u16>,
35    #[bw(try_calc = buffer.size().try_into())]
36    path_length: u16,
37
38    // -- Extension --
39    #[br(if(flags.extension_present()))]
40    #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(PosMarker::default()) })]
41    tree_connect_context_offset: Option<PosMarker<u32>>,
42    #[br(if(flags.extension_present()))]
43    #[bw(if(!tree_connect_contexts.is_empty()))]
44    #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(tree_connect_contexts.len().try_into().unwrap()) })]
45    tree_connect_context_count: Option<u16>,
46    #[br(if(flags.extension_present()))]
47    #[bw(if(!tree_connect_contexts.is_empty()))]
48    #[bw(calc = Some([0u8; 10]))]
49    _reserved: Option<[u8; 10]>,
50    // -- Extension End --
51    // ------------------------------------------------
52    // -- Base --
53    #[brw(little)]
54    #[br(args { size: SizedStringSize::bytes16(path_length) })]
55    #[bw(write_with = PosMarker::write_aoff, args(&_path_offset))]
56    pub buffer: SizedWideString,
57
58    // -- Extension --
59    #[br(if(flags.extension_present()))]
60    #[br(seek_before = tree_connect_context_offset.unwrap().seek_relative(true))]
61    #[br(count = tree_connect_context_count.unwrap())]
62    #[bw(if(!tree_connect_contexts.is_empty()))]
63    #[bw(write_with = PosMarker::write_aoff_m, args(tree_connect_context_offset.as_ref()))]
64    tree_connect_contexts: Vec<TreeConnectContext>,
65}
66
67#[binrw::binrw]
68#[derive(Debug)]
69pub struct TreeConnectContext {
70    /// MS-SMB2 2.2.9.2: Must be set to SMB2_REMOTED_IDENTITY_TREE_CONNECT_CONTEXT_ID = 1.
71    #[bw(calc = 1)]
72    #[br(assert(context_type == 1))]
73    context_type: u16,
74    data_length: u16,
75    reserved: u32,
76    data: RemotedIdentityTreeConnect,
77}
78
79macro_rules! make_remoted_identity_connect{
80    (
81        $($field:ident: $value:ty),*
82    ) => {
83        pastey::paste! {
84
85#[binwrite]
86#[derive(Debug, BinRead)]
87pub struct RemotedIdentityTreeConnect {
88    // MS-SMB2 2.2.9.2.1: Must be set to 0x1.
89    #[bw(calc = PosMarker::new(1))]
90    #[br(assert(_ticket_type.value == 1))]
91    _ticket_type: PosMarker<u16>,
92    ticket_size: u16,
93
94    // Offsets
95    $(
96        #[bw(calc = PosMarker::default())]
97        [<_$field _offset>]: PosMarker<u16>,
98    )*
99
100    // Values
101    $(
102        #[br(seek_before = _ticket_type.seek_from([<_$field _offset>].value as u64))]
103        #[bw(write_with = PosMarker::write_roff_b, args(&[<_$field _offset>], &_ticket_type))]
104        $field: $value,
105    )*
106}
107        }
108    }
109}
110
111make_remoted_identity_connect! {
112    user: SidAttrData,
113    user_name: NullWideString,
114    domain: NullWideString,
115    groups: SidArrayData,
116    restricted_groups: SidArrayData,
117    privileges: PrivilegeArrayData,
118    primary_group: SidArrayData,
119    owner: BlobData<SID>,
120    default_dacl: BlobData<ACL>,
121    device_groups: SidArrayData,
122    user_claims: BlobData<ClaimSecurityAttributeRelativeV1>,
123    device_claims: BlobData<ClaimSecurityAttributeRelativeV1>
124}
125
126#[binrw::binrw]
127#[derive(Debug, PartialEq, Eq)]
128pub struct BlobData<T>
129where
130    T: BinRead + BinWrite,
131    for<'a> <T as BinRead>::Args<'a>: Default,
132    for<'b> <T as BinWrite>::Args<'b>: Default,
133{
134    blob_size: PosMarker<u16>,
135    #[br(map_stream = |s| s.take_seek(blob_size.value as u64))]
136    pub blob_data: T,
137}
138
139#[binrw::binrw]
140#[derive(Debug, PartialEq, Eq)]
141pub struct ArrayData<T>
142where
143    T: BinRead + BinWrite + 'static,
144    for<'a> <T as BinRead>::Args<'a>: Default + Clone,
145    for<'b> <T as BinWrite>::Args<'b>: Default + Clone,
146{
147    #[bw(try_calc = list.len().try_into())]
148    lcount: u16,
149    #[br(count = lcount)]
150    pub list: Vec<T>,
151}
152
153#[binrw::binrw]
154#[derive(Debug, PartialEq, Eq)]
155pub struct SidAttrData {
156    pub sid_data: SID,
157    pub attr: SidAttrSeGroup,
158}
159
160type SidArrayData = ArrayData<SidAttrData>;
161
162#[bitfield]
163#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
164#[bw(map = |&x| Self::into_bytes(x))]
165#[br(map = Self::from_bytes)]
166pub struct SidAttrSeGroup {
167    pub mandatory: bool,
168    pub enabled_by_default: bool,
169    pub group_enabled: bool,
170    pub group_owner: bool,
171    pub group_use_for_deny_only: bool,
172    pub group_integrity: bool,
173    pub group_integrity_enabled: bool,
174    #[skip]
175    __: B21,
176    pub group_logon_id: B4,
177}
178
179#[binrw::binrw]
180#[derive(Debug, PartialEq, Eq)]
181pub struct LuidAttrData {
182    pub luid: u64,
183    pub attr: LsaprLuidAttributes,
184}
185
186#[allow(clippy::identity_op)]
187mod lsapr_luid_attributes {
188    use super::*;
189    /// [MS-LSAD 2.2.5.4](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lsad/03c834c0-f310-4e0c-832e-b6e7688364d1)
190    #[bitfield]
191    #[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
192    #[br(map = Self::from_bytes)]
193    pub struct LsaprLuidAttributes {
194        pub is_default: bool,
195        pub is_enabled: bool,
196        #[skip]
197        __: B30,
198    }
199}
200
201use lsapr_luid_attributes::LsaprLuidAttributes;
202
203type PrivilegeData = BlobData<LuidAttrData>;
204
205type PrivilegeArrayData = ArrayData<PrivilegeData>;
206
207impl TreeConnectRequest {
208    pub fn new(name: &str) -> TreeConnectRequest {
209        TreeConnectRequest {
210            flags: TreeConnectRequestFlags::new(),
211            buffer: name.into(),
212            tree_connect_contexts: vec![],
213        }
214    }
215}
216
217#[binrw::binrw]
218#[derive(Debug, PartialEq, Eq)]
219pub struct TreeConnectResponse {
220    #[bw(calc = 16)]
221    #[br(assert(_structure_size == 16))]
222    _structure_size: u16,
223    pub share_type: ShareType,
224    #[bw(calc = 0)]
225    #[br(assert(_reserved == 0))]
226    _reserved: u8,
227    pub share_flags: ShareFlags,
228    pub capabilities: TreeCapabilities,
229    pub maximal_access: u32,
230}
231
232#[derive(BitfieldSpecifier, Debug, Clone, Copy)]
233#[bits = 4]
234pub enum ShareCacheMode {
235    Manual,
236    Auto,
237    Vdo,
238    NoCache,
239    All = 0xf,
240}
241
242#[bitfield]
243#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
244#[bw(map = |&x| Self::into_bytes(x))]
245#[br(map = Self::from_bytes)]
246pub struct ShareFlags {
247    pub dfs: bool,
248    pub dfs_root: bool,
249    #[skip]
250    __: B2,
251    pub caching_mode: ShareCacheMode,
252
253    pub restrict_exclusive_opens: bool,
254    pub force_shared_delete: bool,
255    pub allow_namespace_caching: bool,
256    pub access_based_directory_enum: bool,
257    pub force_levelii_oplock: bool,
258    pub enable_hash_v1: bool,
259    pub enable_hash_v2: bool,
260    pub encrypt_data: bool,
261
262    #[skip]
263    __: B2,
264    pub identity_remoting: bool,
265    #[skip]
266    __: B1,
267    pub compress_data: bool,
268    pub isolated_transport: bool,
269    #[skip]
270    __: B10,
271}
272
273#[bitfield]
274#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
275#[bw(map = |&x| Self::into_bytes(x))]
276#[br(map = Self::from_bytes)]
277pub struct TreeCapabilities {
278    #[skip]
279    __: B3,
280    pub dfs: bool,
281    pub continuous_availability: bool,
282    pub scaleout: bool,
283    pub cluster: bool,
284    pub asymmetric: bool,
285
286    pub redirect_to_owner: bool,
287    #[skip]
288    __: B23,
289}
290
291#[binrw::binrw]
292#[derive(Debug, PartialEq, Eq, Clone, Copy)]
293#[brw(repr(u8))]
294pub enum ShareType {
295    Disk = 0x1,
296    Pipe = 0x2,
297    Print = 0x3,
298}
299
300#[binrw::binrw]
301#[derive(Debug, Default)]
302pub struct TreeDisconnectRequest {
303    #[bw(calc = 4)]
304    #[br(assert(_structure_size == 4))]
305    _structure_size: u16,
306    #[bw(calc = 0)]
307    #[br(assert(_reserved == 0))]
308    _reserved: u16,
309}
310
311#[binrw::binrw]
312#[derive(Debug)]
313pub struct TreeDisconnectResponse {
314    #[bw(calc = 4)]
315    #[br(assert(_structure_size == 4))]
316    _structure_size: u16,
317    #[bw(calc = 0)]
318    #[br(assert(_reserved == 0))]
319    _reserved: u16,
320}
321
322#[cfg(test)]
323mod tests {
324    use std::io::Cursor;
325
326    use crate::*;
327
328    use super::*;
329
330    #[test]
331    pub fn test_tree_connect_req_write() {
332        let result = encode_content(TreeConnectRequest::new(&r"\\127.0.0.1\MyShare").into());
333        assert_eq!(
334            result,
335            [
336                0x9, 0x0, 0x0, 0x0, 0x48, 0x0, 0x26, 0x0, 0x5c, 0x0, 0x5c, 0x0, 0x31, 0x0, 0x32,
337                0x0, 0x37, 0x0, 0x2e, 0x0, 0x30, 0x0, 0x2e, 0x0, 0x30, 0x0, 0x2e, 0x0, 0x31, 0x0,
338                0x5c, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65,
339                0x0
340            ]
341        );
342    }
343
344    #[test]
345    pub fn test_tree_connect_res_parse() {
346        let mut cursor = Cursor::new(&[
347            0x10, 0x0, 0x1, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x1, 0x1f, 0x0,
348        ]);
349        let content_parsed = TreeConnectResponse::read_le(&mut cursor).unwrap();
350        assert_eq!(
351            content_parsed,
352            TreeConnectResponse {
353                share_type: ShareType::Disk,
354                share_flags: ShareFlags::new().with_access_based_directory_enum(true),
355                capabilities: TreeCapabilities::new(),
356                maximal_access: 0x001f01ff,
357            }
358        )
359    }
360}