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#[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 #[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 #[brw(little)]
54 #[br(args(path_length as u64))]
55 #[bw(write_with = PosMarker::write_aoff, args(&_path_offset))]
56 pub buffer: SizedWideString,
57
58 #[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 #[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 paste::paste! {
84
85#[binwrite]
86#[derive(Debug, BinRead)]
87pub struct RemotedIdentityTreeConnect {
88 #[bw(calc = PosMarker::new(1))]
90 #[br(assert(_ticket_type.value == 1))]
91 _ticket_type: PosMarker<u16>,
92 ticket_size: u16,
93
94 $(
96 #[bw(calc = PosMarker::default())]
97 [<_$field _offset>]: PosMarker<u16>,
98 )*
99
100 $(
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 #[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}