smb_msg/
dfsc.rs

1use binrw::{NullWideString, io::TakeSeekExt, prelude::*};
2use modular_bitfield::prelude::*;
3use smb_dtyp::binrw_util::prelude::*;
4
5/// [MS-DFSC 2.2.2](<https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-dfsc/663c9b38-41b8-4faa-b6f6-a4576b4cea62>):
6/// DFS referral requests are sent in the form of an REQ_GET_DFS_REFERRAL message, by using an appropriate transport as specified in section 2.1.
7#[binrw::binrw]
8#[derive(Debug, PartialEq, Eq)]
9pub struct ReqGetDfsReferral {
10    /// An integer that indicates the highest DFS referral version understood by the client. The DFS referral versions specified by this document are 1 through 4 inclusive. A DFS client MUST support DFS referral version 1 through the version number set in this field. The referral response messages are referral version dependent and are specified in sections 2.2.5.1 through 2.2.5.4.
11    pub max_referral_level: ReferralLevel,
12    /// A null-terminated Unicode string specifying the path to be resolved. The specified path MUST NOT be case-sensitive. Its format depends on the type of referral request, as specified in section 3.1.4.2.
13    pub request_file_name: NullWideString,
14}
15
16/// The DFS referral version supported by the client.
17/// See [`ReqGetDfsReferral::max_referral_level`].
18#[binrw::binrw]
19#[derive(Debug, PartialEq, Eq)]
20#[brw(repr(u16))]
21pub enum ReferralLevel {
22    /// DFS referral version 1
23    V1 = 1,
24    /// DFS referral version 2
25    V2 = 2,
26    /// DFS referral version 3
27    V3 = 3,
28    /// DFS referral version 4
29    V4 = 4,
30}
31
32#[binrw::binrw]
33#[derive(Debug, PartialEq, Eq)]
34pub struct ReqGetDfsReferralEx {
35    /// An integer that indicates the highest DFS referral version understood by the client. The DFS referral versions specified by this document are 1 through 4 inclusive. A DFS client MUST support DFS referral version 1 through the version number set in this field. The referral response messages are referral version dependent and are specified in sections 2.2.5.1 through 2.2.5.4.
36    pub max_referral_level: u16,
37    pub request_flags: DfsRequestFlags,
38    request_data_length: PosMarker<u32>,
39    #[bw(write_with = PosMarker::write_size, args(request_data_length))]
40    #[br(map_stream = |s| s.take_seek(request_data_length.value as u64))]
41    pub request_data: DfsRequestData,
42}
43
44#[bitfield]
45#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
46#[bw(map = |&x| Self::into_bytes(x))]
47#[br(map = Self::from_bytes)]
48pub struct DfsRequestFlags {
49    /// SiteName present: The SiteName bit MUST be set to 1 if the packet contains the site name of the client.
50    pub site_name: bool,
51    #[skip]
52    __: B15,
53}
54
55/// RequestData is part of the REQ_GET_DFS_REFERRAL_EX message (section 2.2.3).
56#[binrw::binrw]
57#[derive(Debug, PartialEq, Eq)]
58pub struct DfsRequestData {
59    #[bw(try_calc = request_file_name.size().try_into())]
60    request_file_name_length: u16,
61    /// A Unicode string specifying the path to be resolved. The specified path MUST be interpreted in a case-insensitive manner. Its format depends on the type of referral request, as specified in section 3.1.4.2.
62    #[br(args { size: SizedStringSize::bytes16(request_file_name_length) })]
63    request_file_name: SizedWideString,
64    #[bw(try_calc = site_name.size().try_into())]
65    site_name_length: u16,
66    /// A Unicode string specifying the name of the site to which the DFS client computer belongs. The length of this string is determined by the value of the SiteNameLength field.
67    #[br(args { size: SizedStringSize::bytes16(site_name_length) })]
68    site_name: SizedWideString,
69}
70
71impl DfsRequestData {
72    pub fn get_bin_size(&self) -> usize {
73        size_of::<u16>() * 2 // lengths
74            + self.request_file_name.len() * size_of::<u16>() // + request_file_name (wstring)
75            + self.site_name.len() * size_of::<u16>() // + site_name (wstring)
76    }
77}
78
79#[binrw::binrw]
80#[derive(Debug, PartialEq, Eq)]
81pub struct RespGetDfsReferral {
82    pub path_consumed: u16,
83    #[bw(try_calc = referral_entries.len().try_into())]
84    number_of_referrals: u16,
85    pub referral_header_flags: ReferralHeaderFlags,
86    #[br(count = number_of_referrals)]
87    pub referral_entries: Vec<ReferralEntry>,
88    // string_buffer is here, but it's use is to provide a buffer for the strings in the referral entries.
89}
90
91#[bitfield]
92#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
93#[bw(map = |&x| Self::into_bytes(x))]
94#[br(map = Self::from_bytes)]
95pub struct ReferralHeaderFlags {
96    /// Whether all of the targets in the referral entries returned are DFS root targets capable of handling DFS referral requests.
97    pub referral_servers: bool,
98    /// Whether all of the targets in the referral response can be accessed without requiring further referral requests.
99    pub storage_servers: bool,
100    /// Whether DFS client target failback is enabled for all targets in this referral response. This value used only in version 4.
101    pub target_failbacl: bool,
102    #[skip]
103    __: B29,
104}
105
106#[binrw::binrw]
107#[derive(Debug, PartialEq, Eq)]
108pub struct ReferralEntry {
109    #[bw(calc = value.get_version())]
110    pub version: u16,
111    #[bw(calc = PosMarker::default())]
112    _size: PosMarker<u16>,
113    #[br(args(version))]
114    #[bw(write_with = PosMarker::write_size, args(&_size))]
115    pub value: ReferralEntryValue,
116}
117
118impl ReferralEntry {
119    /// The size of the common part of the referral entry - version + size.
120    pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2;
121}
122
123macro_rules! gen_ref_entry_val {
124    (
125        $($ver:literal,)+
126    ) => {
127        pastey::paste! {
128        #[binrw::binrw]
129        #[derive(Debug, PartialEq, Eq)]
130        #[br(import(version: u16))]
131        pub enum ReferralEntryValue {
132            $(
133                #[doc = concat!("A DFS referral version", stringify!($ver), "Entry")]
134                #[br(pre_assert(version == $ver))]
135                [<V $ver>]([<ReferralEntryValueV $ver>]),
136            )+
137        }
138
139        impl ReferralEntryValue {
140            fn get_version(&self) -> u16 {
141                match self {
142                    $(
143                        Self::[<V $ver>](_) => $ver,
144                    )+
145                }
146            }
147        }
148                }
149    };
150}
151
152gen_ref_entry_val!(1, 2, 3, 4,);
153
154#[binrw::binrw]
155#[derive(Debug, PartialEq, Eq)]
156pub struct ReferralEntryValueV1 {
157    /// Type of server hosting the target
158    pub server_type: DfsServerType,
159    #[bw(calc = 0)]
160    _referral_entry_flags: u16,
161    /// The DFS target.
162    pub share_name: NullWideString,
163}
164
165/// Type of server hosting the target
166#[binrw::binrw]
167#[derive(Debug, PartialEq, Eq)]
168#[brw(repr(u16))]
169pub enum DfsServerType {
170    /// Non-root targets returned.
171    NonRoot = 0x0,
172    /// Root targets returned.
173    Root = 0x1,
174}
175
176/// DO NOT use this struct directly when bin read/writing.
177/// Use an instance of [`ReferralEntry`] instead.
178#[binrw::binrw]
179#[derive(Debug, PartialEq, Eq)]
180pub struct ReferralEntryValueV2 {
181    #[bw(calc = PosMarker::default())]
182    _start: PosMarker<()>,
183    /// Type of server hosting the target
184    pub server_type: DfsServerType,
185    #[bw(calc = 0)]
186    _referral_entry_flags: u16,
187    #[bw(calc = 0)]
188    _proximity: u32,
189
190    /// The time-out value, in seconds, of the DFS root or DFS link.
191    pub time_to_live: u32,
192    #[br(assert(dfs_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
193    #[bw(calc = PosMarker::default())]
194    dfs_path_offset: PosMarker<u16>,
195    #[br(assert(dfs_alternate_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
196    #[bw(calc = PosMarker::default())]
197    dfs_alternate_path_offset: PosMarker<u16>,
198    #[br(assert(network_address_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
199    #[bw(calc = PosMarker::default())]
200    network_address_offset: PosMarker<u16>,
201
202    #[bw(calc = PosMarker::default())]
203    _restore_position: PosMarker<()>,
204
205    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
206    #[br(seek_before = _start.seek_from((dfs_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
207    pub dfs_path: NullWideString,
208    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
209    #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
210    pub dfs_alternate_path: NullWideString,
211    /// The DFS target that corresponds to this entry.
212    #[br(seek_before = _start.seek_from((network_address_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
213    pub network_address: NullWideString,
214
215    #[br(seek_before = _restore_position.seek_from(0))]
216    #[bw(calc = ())]
217    __: (),
218}
219
220#[binrw::binrw]
221#[derive(Debug, PartialEq, Eq)]
222pub struct ReferralEntryValueV3 {
223    /// Type of server hosting the target
224    pub server_type: DfsServerType,
225    pub referral_entry_flags: ReferralEntryFlags,
226    /// The time-out value, in seconds, of the DFS root or DFS link.
227    pub time_to_live: u32,
228    #[br(args(referral_entry_flags))]
229    pub value: EntryV3Value,
230}
231
232impl ReferralEntryValueV3 {
233    /// The size of the common part of the referral entry - version + size.
234    pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2 + std::mem::size_of::<u32>();
235}
236
237#[bitfield]
238#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
239#[bw(map = |&x| Self::into_bytes(x))]
240#[br(map = Self::from_bytes)]
241pub struct ReferralEntryFlags {
242    #[skip]
243    __: bool,
244    pub name_list_referral: bool,
245    #[skip]
246    __: B14,
247}
248
249#[binrw::binrw]
250#[derive(Debug, PartialEq, Eq)]
251#[br(import(flags: ReferralEntryFlags))]
252pub enum EntryV3Value {
253    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
254    #[br(pre_assert(flags.name_list_referral()))]
255    DfsPath(EntryV3V4DfsPaths),
256    /// The DFS target that corresponds to this entry.
257    #[br(pre_assert(!flags.name_list_referral()))]
258    NetworkAddress(EntryV3DCRefs),
259}
260
261impl EntryV3Value {
262    /// (Internal)
263    ///
264    /// The offset of EntryV3Value from the beginning of the [`ReferralEntry`] structure.
265    /// This is used to calculate the offsets of the fields in the structure.
266    const OFFSET_FROM_ENTRY_START: u16 =
267        (ReferralEntry::COMMON_PART_SIZE + ReferralEntryValueV3::COMMON_PART_SIZE) as u16;
268}
269
270/// 2.2.5.3.1 NameListReferral Flag Set to 0
271#[binrw::binrw]
272#[derive(Debug, PartialEq, Eq)]
273pub struct EntryV3V4DfsPaths {
274    #[bw(calc = PosMarker::default())]
275    _start: PosMarker<()>,
276    #[br(assert(dfs_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
277    #[bw(calc = PosMarker::default())]
278    dfs_path_offset: PosMarker<u16>,
279    #[br(assert(dfs_alternate_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
280    #[bw(calc = PosMarker::default())]
281    dfs_alternate_path_offset: PosMarker<u16>,
282    #[br(assert(network_address_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
283    #[bw(calc = PosMarker::default())]
284    network_address_offset: PosMarker<u16>,
285    #[bw(calc = 0)]
286    _service_site_guid: u128,
287
288    #[bw(calc = PosMarker::default())]
289    _restore_position: PosMarker<()>,
290
291    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
292    #[br(seek_before = _start.seek_from((dfs_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
293    pub dfs_path: NullWideString,
294    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
295    #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
296    pub dfs_alternate_path: NullWideString,
297    /// The DFS target that corresponds to this entry.
298    #[br(seek_before = _start.seek_from((network_address_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
299    pub network_address: NullWideString,
300
301    #[br(seek_before = _restore_position.seek_from(0))]
302    #[bw(calc = ())]
303    __: (),
304}
305
306/// 2.2.5.3.2 NameListReferral Flag Set to 1
307#[binrw::binrw]
308#[derive(Debug, PartialEq, Eq)]
309pub struct EntryV3DCRefs {
310    #[bw(calc = PosMarker::default())]
311    _start: PosMarker<()>,
312    #[br(assert(special_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
313    #[bw(calc = PosMarker::default())]
314    special_name_offset: PosMarker<u16>,
315    number_of_expanded_names: u16,
316    #[br(assert(expanded_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
317    #[bw(calc = PosMarker::default())]
318    expanded_name_offset: PosMarker<u16>,
319
320    #[bw(calc = PosMarker::default())]
321    _restore_position: PosMarker<()>,
322
323    #[br(seek_before = _start.seek_from((special_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
324    pub special_name: NullWideString,
325    #[br(seek_before = _start.seek_from((expanded_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
326    #[br(count = number_of_expanded_names)]
327    pub expanded_names: Vec<NullWideString>,
328
329    #[br(seek_before = _restore_position.seek_from(0))]
330    #[bw(calc = ())]
331    __: (),
332}
333
334#[binrw::binrw]
335#[derive(Debug, PartialEq, Eq)]
336pub struct ReferralEntryValueV4 {
337    /// Type of server hosting the target
338    pub server_type: DfsServerType,
339    // The ONLY valid flag is TargetSetBoundary.
340    #[br(assert((referral_entry_flags & !u16::from_le_bytes(ReferralEntryFlagsV4::new().with_target_set_boundary(true).into_bytes())) == 0))]
341    pub referral_entry_flags: u16,
342    /// The time-out value, in seconds, of the DFS root or DFS link.
343    pub time_to_live: u32,
344    // name_list_referral: bool is ALWAYS 0, so we know the type of the value.
345    pub refs: EntryV3V4DfsPaths,
346}
347
348/// Internal.
349#[bitfield]
350#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
351#[bw(map = |&x| Self::into_bytes(x))]
352#[br(map = Self::from_bytes)]
353struct ReferralEntryFlagsV4 {
354    #[skip]
355    __: B2,
356    #[skip(getters)]
357    target_set_boundary: bool,
358    #[skip]
359    __: B13,
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365    use crate::IoctlRequestContent;
366    use std::io::Cursor;
367
368    #[test]
369    pub fn test_write_req() {
370        let req = ReqGetDfsReferral {
371            max_referral_level: ReferralLevel::V4,
372            request_file_name: r"\ADC.aviv.local\dfs\Docs".into(),
373        };
374        let mut buf = Vec::new();
375        req.write_le(&mut Cursor::new(&mut buf)).unwrap();
376        assert_eq!(buf.len() as u32, req.get_bin_size());
377        assert_eq!(
378            buf,
379            &[
380                0x4, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76,
381                0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x61, 0x0,
382                0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f,
383                0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0
384            ]
385        );
386    }
387
388    #[test]
389    pub fn test_v4_parses_properly() {
390        let bytes = [
391            0x30, 0x0, 0x2, 0x0, 0x2, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x4, 0x0, 0x8,
392            0x7, 0x0, 0x0, 0x44, 0x0, 0x76, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
393            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x22, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8,
394            0x7, 0x0, 0x0, 0x22, 0x0, 0x54, 0x0, 0xa8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
395            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0, 0x43, 0x0,
396            0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0, 0x6f, 0x0,
397            0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0, 0x5c, 0x0,
398            0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0, 0x44, 0x0,
399            0x43, 0x0, 0x2e, 0x0, 0x61, 0x0, 0x76, 0x0, 0x69, 0x0, 0x76, 0x0, 0x2e, 0x0, 0x6c, 0x0,
400            0x6f, 0x0, 0x63, 0x0, 0x61, 0x0, 0x6c, 0x0, 0x5c, 0x0, 0x64, 0x0, 0x66, 0x0, 0x73, 0x0,
401            0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0, 0x41, 0x0,
402            0x44, 0x0, 0x43, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0, 0x72, 0x0, 0x65, 0x0,
403            0x73, 0x0, 0x5c, 0x0, 0x44, 0x0, 0x6f, 0x0, 0x63, 0x0, 0x73, 0x0, 0x0, 0x0, 0x5c, 0x0,
404            0x46, 0x0, 0x53, 0x0, 0x52, 0x0, 0x56, 0x0, 0x5c, 0x0, 0x53, 0x0, 0x68, 0x0, 0x61, 0x0,
405            0x72, 0x0, 0x65, 0x0, 0x73, 0x0, 0x5c, 0x0, 0x4d, 0x0, 0x79, 0x0, 0x53, 0x0, 0x68, 0x0,
406            0x61, 0x0, 0x72, 0x0, 0x65, 0x0, 0x0, 0x0,
407        ];
408
409        let r = RespGetDfsReferral::read_le(&mut Cursor::new(&bytes)).unwrap();
410        assert_eq!(
411            r,
412            RespGetDfsReferral {
413                path_consumed: 48,
414                referral_header_flags: ReferralHeaderFlags::new().with_storage_servers(true),
415                referral_entries: vec![
416                    ReferralEntry {
417                        value: ReferralEntryValue::V4(ReferralEntryValueV4 {
418                            server_type: DfsServerType::NonRoot,
419                            referral_entry_flags: u16::from_le_bytes(
420                                ReferralEntryFlagsV4::new()
421                                    .with_target_set_boundary(true)
422                                    .into_bytes()
423                            ),
424                            time_to_live: 1800,
425                            refs: EntryV3V4DfsPaths {
426                                dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
427                                dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
428                                network_address: r"\ADC\Shares\Docs".into()
429                            }
430                        })
431                    },
432                    ReferralEntry {
433                        value: ReferralEntryValue::V4(ReferralEntryValueV4 {
434                            server_type: DfsServerType::NonRoot,
435                            referral_entry_flags: 0,
436                            time_to_live: 1800,
437                            refs: EntryV3V4DfsPaths {
438                                dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
439                                dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
440                                network_address: r"\FSRV\Shares\MyShare".into()
441                            }
442                        })
443                    }
444                ]
445            }
446        )
447    }
448}