smb_msg/
tree_connect.rs

1//! Tree (share) connect & disconnect messages
2
3use binrw::prelude::*;
4use binrw::{NullWideString, io::TakeSeekExt};
5use modular_bitfield::prelude::*;
6use smb_dtyp::{
7    binrw_util::prelude::*,
8    security::{ACL, ClaimSecurityAttributeRelativeV1, SID},
9};
10
11/// Flags for SMB2 TREE_CONNECT Request
12///
13/// Reference: MS-SMB2 2.2.9
14#[smb_dtyp::mbitfield]
15pub struct TreeConnectRequestFlags {
16    /// Client has previously connected to the specified cluster share using the SMB dialect of the connection
17    pub cluster_reconnect: bool,
18    /// Client can handle synchronous share redirects via a Share Redirect error context response
19    pub redirect_to_owner: bool,
20    /// Tree connect request extension is present, starting at the Buffer field
21    pub extension_present: bool,
22    #[skip]
23    __: B13,
24}
25
26/// SMB2 TREE_CONNECT Request
27///
28/// Sent by a client to request access to a particular share on the server.
29/// Supports both the base and extension variants.
30/// - On read, uses extension iff `flags.extension_present()` - parses just like the server intends.
31/// - On write, uses extension iff `tree_connect_contexts` is non-empty.
32///
33/// Reference: MS-SMB2 2.2.9
34#[smb_request(size = 9)]
35pub struct TreeConnectRequest {
36    /// Flags indicating how to process the operation
37    pub flags: TreeConnectRequestFlags,
38    #[bw(calc = PosMarker::default())]
39    #[br(temp)]
40    _path_offset: PosMarker<u16>,
41    #[bw(try_calc = buffer.size().try_into())]
42    #[br(temp)]
43    path_length: u16,
44
45    // -- Extension --
46    #[br(if(flags.extension_present()))]
47    #[br(temp)]
48    #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(PosMarker::default()) })]
49    tree_connect_context_offset: Option<PosMarker<u32>>,
50
51    #[br(if(flags.extension_present()))]
52    #[bw(if(!tree_connect_contexts.is_empty()))]
53    #[bw(calc = if tree_connect_contexts.is_empty() { None } else { Some(tree_connect_contexts.len().try_into().unwrap()) })]
54    #[br(temp)]
55    tree_connect_context_count: Option<u16>,
56
57    #[br(if(flags.extension_present()))]
58    #[bw(if(!tree_connect_contexts.is_empty()))]
59    #[bw(calc = Some([0u8; 10]))]
60    #[br(temp)]
61    _reserved: Option<[u8; 10]>,
62    // -- Extension End --
63    // ------------------------------------------------
64    // -- Base --
65    #[brw(little)]
66    #[br(args { size: SizedStringSize::bytes16(path_length) })]
67    #[bw(write_with = PosMarker::write_aoff, args(&_path_offset))]
68    /// Full share path name in Unicode format "\\server\share"
69    pub buffer: SizedWideString,
70
71    // -- Extension --
72    #[br(if(flags.extension_present()))]
73    #[br(seek_before = tree_connect_context_offset.unwrap().seek_relative(true))]
74    #[br(count = tree_connect_context_count.unwrap_or(0))]
75    #[bw(if(!tree_connect_contexts.is_empty()))]
76    #[bw(write_with = PosMarker::write_aoff_m, args(tree_connect_context_offset.as_ref()))]
77    tree_connect_contexts: Vec<TreeConnectContext>,
78}
79
80/// SMB2 TREE_CONNECT_CONTEXT Request structure
81///
82/// Used to encode additional properties in SMB2 TREE_CONNECT requests and responses.
83///
84/// Reference: MS-SMB2 2.2.9.2
85#[smb_request_binrw]
86pub struct TreeConnectContext {
87    /// Type of context in the Data field
88    #[bw(calc = 1)]
89    #[br(assert(context_type == 1))]
90    context_type: u16,
91    /// Length in bytes of the Data field
92    data_length: u16,
93    reserved: u32,
94    data: RemotedIdentityTreeConnect,
95}
96
97macro_rules! make_remoted_identity_connect{
98    (
99        $($field:ident: $value:ty),*
100    ) => {
101        pastey::paste! {
102
103#[binwrite]
104#[derive(Debug, BinRead, PartialEq, Eq)]
105/// SMB2_REMOTED_IDENTITY_TREE_CONNECT Context
106///
107/// Contains remoted identity tree connect context data with user information,
108/// groups, privileges, and other security attributes.
109///
110/// Reference: MS-SMB2 2.2.9.2.1
111pub struct RemotedIdentityTreeConnect {
112    #[bw(calc = PosMarker::new(1))]
113    #[br(assert(_ticket_type.value == 1))]
114    _ticket_type: PosMarker<u16>,
115    /// Total size of this structure
116    ticket_size: u16,
117
118    // Offsets
119    $(
120        #[bw(calc = PosMarker::default())]
121        #[br(temp)]
122        [<_$field _offset>]: PosMarker<u16>,
123    )*
124
125    // Values
126    $(
127        #[br(seek_before = _ticket_type.seek_from([<_$field _offset>].value as u64))]
128        #[bw(write_with = PosMarker::write_roff_b, args(&[<_$field _offset>], &_ticket_type))]
129        $field: $value,
130    )*
131}
132        }
133    }
134}
135
136make_remoted_identity_connect! {
137    user: SidAttrData,
138    user_name: NullWideString,
139    domain: NullWideString,
140    groups: SidArrayData,
141    restricted_groups: SidArrayData,
142    privileges: PrivilegeArrayData,
143    primary_group: SidArrayData,
144    owner: BlobData<SID>,
145    default_dacl: BlobData<ACL>,
146    device_groups: SidArrayData,
147    user_claims: BlobData<ClaimSecurityAttributeRelativeV1>,
148    device_claims: BlobData<ClaimSecurityAttributeRelativeV1>
149}
150
151/// BLOB_DATA structure containing variable-length binary data
152///
153/// Reference: MS-SMB2 2.2.9.2.1.1
154#[binrw::binrw]
155#[derive(Debug, PartialEq, Eq)]
156pub struct BlobData<T>
157where
158    T: BinRead + BinWrite,
159    for<'a> <T as BinRead>::Args<'a>: Default,
160    for<'b> <T as BinWrite>::Args<'b>: Default,
161{
162    /// Size of the blob data
163    blob_size: PosMarker<u16>,
164    #[br(map_stream = |s| s.take_seek(blob_size.value as u64))]
165    pub blob_data: T,
166}
167
168/// Array data structure for variable-length arrays
169#[binrw::binrw]
170#[derive(Debug, PartialEq, Eq)]
171pub struct ArrayData<T>
172where
173    T: BinRead + BinWrite + 'static,
174    for<'a> <T as BinRead>::Args<'a>: Default + Clone,
175    for<'b> <T as BinWrite>::Args<'b>: Default + Clone,
176{
177    #[bw(try_calc = list.len().try_into())]
178    lcount: u16,
179    #[br(count = lcount)]
180    pub list: Vec<T>,
181}
182
183/// SID_ATTR_DATA structure containing SID and attributes
184///
185/// Reference: MS-SMB2 2.2.9.2.1.2
186#[binrw::binrw]
187#[derive(Debug, PartialEq, Eq)]
188pub struct SidAttrData {
189    /// Security identifier
190    pub sid_data: SID,
191    /// Attributes associated with the SID
192    pub attr: SidAttrSeGroup,
193}
194
195type SidArrayData = ArrayData<SidAttrData>;
196
197/// SE_GROUP attributes for SID
198///
199/// Reference: MS-SMB2 2.2.9.2.1.2
200#[smb_dtyp::mbitfield]
201pub struct SidAttrSeGroup {
202    /// This SID is mandatory
203    pub mandatory: bool,
204    /// This SID is enabled by default
205    pub enabled_by_default: bool,
206    /// This SID is enabled for access checks
207    pub group_enabled: bool,
208    /// This SID is the owner SID for objects created by this user
209    pub group_owner: bool,
210    /// This SID cannot be disabled
211    pub group_use_for_deny_only: bool,
212    /// This SID identifies an integrity level
213    pub group_integrity: bool,
214    /// This SID is integrity-enabled
215    pub group_integrity_enabled: bool,
216    #[skip]
217    __: B21,
218    /// Identifies the logon session
219    pub group_logon_id: B4,
220}
221
222/// LUID_ATTR_DATA structure containing LUID and attributes
223#[binrw::binrw]
224#[derive(Debug, PartialEq, Eq)]
225pub struct LuidAttrData {
226    /// Locally unique identifier
227    pub luid: u64,
228    /// Attributes for the LUID
229    pub attr: LsaprLuidAttributes,
230}
231
232mod lsapr_luid_attributes {
233    use super::*;
234    /// LSAPR_LUID_ATTRIBUTES structure
235    ///
236    /// Reference: MS-LSAD 2.2.5.4
237    #[smb_dtyp::mbitfield]
238    pub struct LsaprLuidAttributes {
239        /// Default privilege that is enabled by default
240        pub is_default: bool,
241        /// Privilege is enabled
242        pub is_enabled: bool,
243        #[skip]
244        __: B30,
245    }
246}
247
248use lsapr_luid_attributes::LsaprLuidAttributes;
249use smb_msg_derive::*;
250
251type PrivilegeData = BlobData<LuidAttrData>;
252
253type PrivilegeArrayData = ArrayData<PrivilegeData>;
254
255impl TreeConnectRequest {
256    pub fn new(name: &str) -> TreeConnectRequest {
257        TreeConnectRequest {
258            flags: TreeConnectRequestFlags::new(),
259            buffer: name.into(),
260            tree_connect_contexts: vec![],
261        }
262    }
263}
264
265/// SMB2 TREE_CONNECT Response
266///
267/// Sent by the server when an SMB2 TREE_CONNECT request is processed successfully.
268///
269/// Reference: MS-SMB2 2.2.10
270#[smb_response(size = 16)]
271pub struct TreeConnectResponse {
272    /// Type of share being accessed
273    pub share_type: ShareType,
274    reserved: u8,
275    /// Properties for this share
276    pub share_flags: ShareFlags,
277    /// Capabilities for this share
278    pub capabilities: TreeCapabilities,
279    /// Maximal access for the user that establishes the tree connect on the share
280    pub maximal_access: u32,
281}
282
283/// Share caching mode for offline file access
284#[derive(BitfieldSpecifier, Debug, Clone, Copy)]
285#[bits = 4]
286pub enum ShareCacheMode {
287    /// Manual caching - client can cache files explicitly selected by user
288    Manual,
289    /// Automatic caching - client can automatically cache files used by user
290    Auto,
291    /// VDO caching - client can use cached files even when share is available
292    Vdo,
293    /// No caching - offline caching must not occur
294    NoCache,
295    All = 0xf,
296}
297
298/// Share flags indicating various share properties
299///
300/// Reference: MS-SMB2 2.2.10
301#[smb_dtyp::mbitfield]
302pub struct ShareFlags {
303    /// Share is present in a Distributed File System tree structure
304    pub dfs: bool,
305    /// Share is present in a DFS Root tree structure
306    pub dfs_root: bool,
307    #[skip]
308    __: B2,
309    /// Offline caching behavior for this share
310    pub caching_mode: ShareCacheMode,
311
312    /// Share disallows exclusive file opens that deny reads to an open file
313    pub restrict_exclusive_opens: bool,
314    /// Share disallows clients from opening files in exclusive mode that prevents deletion
315    pub force_shared_delete: bool,
316    /// Namespace caching is allowed (client must ignore this flag)
317    pub allow_namespace_caching: bool,
318    /// Server will filter directory entries based on client access permissions
319    pub access_based_directory_enum: bool,
320    /// Server will not issue exclusive caching rights on this share
321    pub force_levelii_oplock: bool,
322    /// Share supports hash generation for branch cache retrieval of data
323    pub enable_hash_v1: bool,
324    /// Share supports v2 hash generation for branch cache retrieval of data
325    pub enable_hash_v2: bool,
326    /// Server requires encryption of remote file access messages on this share
327    pub encrypt_data: bool,
328
329    #[skip]
330    __: B2,
331    /// Share supports identity remoting via SMB2_REMOTED_IDENTITY_TREE_CONNECT context
332    pub identity_remoting: bool,
333    #[skip]
334    __: B1,
335    /// Server supports compression of read/write messages on this share
336    pub compress_data: bool,
337    /// Server indicates preference to isolate communication on separate connections
338    pub isolated_transport: bool,
339    #[skip]
340    __: B10,
341}
342
343/// Tree capabilities indicating various share capabilities
344///
345/// Reference: MS-SMB2 2.2.10
346#[smb_dtyp::mbitfield]
347pub struct TreeCapabilities {
348    #[skip]
349    __: B3,
350    /// Share is present in a DFS tree structure
351    pub dfs: bool,
352    /// Share is continuously available
353    pub continuous_availability: bool,
354    /// Share facilitates faster recovery of durable handles
355    pub scaleout: bool,
356    /// Share provides monitoring through the Witness service
357    pub cluster: bool,
358    /// Share allows dynamic changes in ownership
359    pub asymmetric: bool,
360
361    /// Share supports synchronous share level redirection
362    pub redirect_to_owner: bool,
363    #[skip]
364    __: B23,
365}
366
367/// Type of share being accessed
368///
369/// Reference: MS-SMB2 2.2.10
370#[smb_response_binrw]
371#[derive(Clone, Copy)]
372#[brw(repr(u8))]
373pub enum ShareType {
374    /// Physical disk share
375    Disk = 0x1,
376    /// Named pipe share
377    Pipe = 0x2,
378    /// Printer share
379    Print = 0x3,
380}
381
382/// SMB2 TREE_DISCONNECT Request
383///
384/// Sent by a client to request that the tree connect that is specified in the TreeId within
385/// the SMB2 header be disconnected.
386///
387/// Reference: MS-SMB2 2.2.11
388#[smb_request(size = 4)]
389#[derive(Default)]
390pub struct TreeDisconnectRequest {
391    reserved: u16,
392}
393
394/// SMB2 TREE_DISCONNECT Response
395///
396/// Sent by the server when an SMB2 TREE_DISCONNECT Request is processed successfully.
397///
398/// Reference: MS-SMB2 2.2.12
399#[smb_response(size = 4)]
400#[derive(Default)]
401pub struct TreeDisconnectResponse {
402    reserved: u16,
403}
404
405#[cfg(test)]
406mod tests {
407    use crate::*;
408
409    use super::*;
410
411    // TODO(test): Add tests with tree connect contexts.
412    test_request! {
413        TreeConnect {
414            flags: TreeConnectRequestFlags::new(),
415            buffer: r"\\adc.aviv.local\IPC$".into(),
416            tree_connect_contexts: vec![],
417        } => "0900000048002a005c005c006100640063002e0061007600690076002e006c006f00630061006c005c004900500043002400"
418    }
419
420    test_binrw_response! {
421        struct TreeConnectResponse {
422            share_type: ShareType::Disk,
423            share_flags: ShareFlags::new().with_access_based_directory_enum(true),
424            capabilities: TreeCapabilities::new(),
425            maximal_access: 0x001f01ff,
426        } => "100001000008000000000000ff011f00"
427    }
428}