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/// NOTE: This struct currently implements [`BinWrite`] only as a placeholder (calling it will panic).
80#[binrw::binread]
81#[derive(Debug, PartialEq, Eq)]
82pub struct RespGetDfsReferral {
83    pub path_consumed: u16,
84    // #[bw(try_calc = referral_entries.len().try_into())]
85    #[br(temp)]
86    number_of_referrals: u16,
87    pub referral_header_flags: ReferralHeaderFlags,
88    #[br(count = number_of_referrals)]
89    pub referral_entries: Vec<ReferralEntry>,
90    // string_buffer is here, but it's use is to provide a buffer for the strings in the referral entries.
91}
92
93impl BinWrite for RespGetDfsReferral {
94    type Args<'a> = ();
95
96    fn write_options<W: binrw::io::Write + binrw::io::Seek>(
97        &self,
98        _writer: &mut W,
99        _endian: binrw::Endian,
100        _args: Self::Args<'_>,
101    ) -> binrw::BinResult<()> {
102        unimplemented!(
103            "Placeholder trait implementation for RespGetDfsReferral - writing is currently not supported"
104        );
105    }
106}
107
108#[bitfield]
109#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
110#[bw(map = |&x| Self::into_bytes(x))]
111#[br(map = Self::from_bytes)]
112pub struct ReferralHeaderFlags {
113    /// Whether all of the targets in the referral entries returned are DFS root targets capable of handling DFS referral requests.
114    pub referral_servers: bool,
115    /// Whether all of the targets in the referral response can be accessed without requiring further referral requests.
116    pub storage_servers: bool,
117    /// Whether DFS client target failback is enabled for all targets in this referral response. This value used only in version 4.
118    pub target_failbacl: bool,
119    #[skip]
120    __: B29,
121}
122
123#[binrw::binrw]
124#[derive(Debug, PartialEq, Eq)]
125pub struct ReferralEntry {
126    /* All entry types share the same fields in their beginnings, so we split it */
127    #[bw(calc = value.get_version())]
128    pub version: u16,
129    #[bw(calc = PosMarker::default())]
130    _size: PosMarker<u16>,
131
132    #[br(args(version))]
133    // map_stream is not used here because we seek manually in the inner structs.
134    #[bw(write_with = PosMarker::write_size_plus, args(&_size, Self::COMMON_PART_SIZE as u64))]
135    pub value: ReferralEntryValue,
136}
137
138impl ReferralEntry {
139    /// The size of the common part of the referral entry - version + size.
140    pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2;
141}
142
143macro_rules! gen_ref_entry_val {
144    (
145        $($ver:literal,)+
146    ) => {
147        pastey::paste! {
148        #[binrw::binrw]
149        #[derive(Debug, PartialEq, Eq)]
150        #[br(import(version: u16))]
151        pub enum ReferralEntryValue {
152            $(
153                #[doc = concat!("A DFS referral version", stringify!($ver), "Entry")]
154                #[br(pre_assert(version == $ver))]
155                [<V $ver>]([<ReferralEntryValueV $ver>]),
156            )+
157        }
158
159        impl ReferralEntryValue {
160            fn get_version(&self) -> u16 {
161                match self {
162                    $(
163                        Self::[<V $ver>](_) => $ver,
164                    )+
165                }
166            }
167        }
168                }
169    };
170}
171
172gen_ref_entry_val!(1, 2, 3, 4,);
173
174#[binrw::binrw]
175#[derive(Debug, PartialEq, Eq)]
176pub struct ReferralEntryValueV1 {
177    /// Type of server hosting the target
178    pub server_type: DfsServerType,
179    #[bw(calc = 0)]
180    _referral_entry_flags: u16,
181    /// The DFS target.
182    pub share_name: NullWideString,
183}
184
185/// Type of server hosting the target
186#[binrw::binrw]
187#[derive(Debug, PartialEq, Eq)]
188#[brw(repr(u16))]
189pub enum DfsServerType {
190    /// Non-root targets returned.
191    NonRoot = 0x0,
192    /// Root targets returned.
193    Root = 0x1,
194}
195
196/// DO NOT use this struct directly when bin read/writing.
197/// Use an instance of [`ReferralEntry`] instead.
198#[binrw::binrw]
199#[derive(Debug, PartialEq, Eq)]
200pub struct ReferralEntryValueV2 {
201    #[bw(calc = PosMarker::default())]
202    _start: PosMarker<()>,
203    /// Type of server hosting the target
204    pub server_type: DfsServerType,
205    #[bw(calc = 0)]
206    _referral_entry_flags: u16,
207    #[bw(calc = 0)]
208    _proximity: u32,
209
210    /// The time-out value, in seconds, of the DFS root or DFS link.
211    pub time_to_live: u32,
212    #[br(assert(dfs_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
213    #[bw(calc = PosMarker::default())]
214    dfs_path_offset: PosMarker<u16>,
215    #[br(assert(dfs_alternate_path_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
216    #[bw(calc = PosMarker::default())]
217    dfs_alternate_path_offset: PosMarker<u16>,
218    #[br(assert(network_address_offset.value >= ReferralEntry::COMMON_PART_SIZE as u16))]
219    #[bw(calc = PosMarker::default())]
220    network_address_offset: PosMarker<u16>,
221
222    #[bw(calc = PosMarker::default())]
223    _restore_position: PosMarker<()>,
224
225    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
226    #[br(seek_before = _start.seek_from((dfs_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
227    #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
228    pub dfs_path: NullWideString,
229    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
230    #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
231    #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_alternate_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
232    pub dfs_alternate_path: NullWideString,
233    /// The DFS target that corresponds to this entry.
234    #[br(seek_before = _start.seek_from((network_address_offset.value as usize - ReferralEntry::COMMON_PART_SIZE).try_into().unwrap()))]
235    #[bw(write_with = PosMarker::write_roff_b_plus, args(&network_address_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
236    pub network_address: NullWideString,
237
238    #[br(seek_before = _restore_position.seek_from(0))]
239    #[bw(calc = ())]
240    __: (),
241}
242
243#[binrw::binrw]
244#[derive(Debug, PartialEq, Eq)]
245pub struct ReferralEntryValueV3 {
246    /// Type of server hosting the target
247    pub server_type: DfsServerType,
248    pub referral_entry_flags: ReferralEntryFlags,
249    /// The time-out value, in seconds, of the DFS root or DFS link.
250    pub time_to_live: u32,
251    #[br(args(referral_entry_flags))]
252    pub value: EntryV3Value,
253}
254
255impl ReferralEntryValueV3 {
256    /// The size of the common part of the referral entry - version + size.
257    pub const COMMON_PART_SIZE: usize = std::mem::size_of::<u16>() * 2 + std::mem::size_of::<u32>();
258}
259
260#[bitfield]
261#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
262#[bw(map = |&x| Self::into_bytes(x))]
263#[br(map = Self::from_bytes)]
264pub struct ReferralEntryFlags {
265    #[skip]
266    __: bool,
267    pub name_list_referral: bool,
268    #[skip]
269    __: B14,
270}
271
272#[binrw::binrw]
273#[derive(Debug, PartialEq, Eq)]
274#[br(import(flags: ReferralEntryFlags))]
275pub enum EntryV3Value {
276    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
277    #[br(pre_assert(flags.name_list_referral()))]
278    DfsPath(EntryV3V4DfsPaths),
279    /// The DFS target that corresponds to this entry.
280    #[br(pre_assert(!flags.name_list_referral()))]
281    NetworkAddress(EntryV3DCRefs),
282}
283
284impl EntryV3Value {
285    /// (Internal)
286    ///
287    /// The offset of EntryV3Value from the beginning of the [`ReferralEntry`] structure.
288    /// This is used to calculate the offsets of the fields in the structure.
289    const OFFSET_FROM_ENTRY_START: u16 =
290        (ReferralEntry::COMMON_PART_SIZE + ReferralEntryValueV3::COMMON_PART_SIZE) as u16;
291}
292
293/// 2.2.5.3.1 NameListReferral Flag Set to 0
294#[binrw::binrw]
295#[derive(Debug, PartialEq, Eq)]
296pub struct EntryV3V4DfsPaths {
297    #[bw(calc = PosMarker::default())]
298    _start: PosMarker<()>,
299    #[br(assert(dfs_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
300    #[bw(calc = PosMarker::default())]
301    dfs_path_offset: PosMarker<u16>,
302    #[br(assert(dfs_alternate_path_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
303    #[bw(calc = PosMarker::default())]
304    dfs_alternate_path_offset: PosMarker<u16>,
305    #[br(assert(network_address_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
306    #[bw(calc = PosMarker::default())]
307    network_address_offset: PosMarker<u16>,
308    #[bw(calc = 0)]
309    _service_site_guid: u128,
310
311    #[bw(calc = PosMarker::default())]
312    _restore_position: PosMarker<()>,
313
314    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
315    #[br(seek_before = _start.seek_from((dfs_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
316    #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
317    pub dfs_path: NullWideString,
318    /// The DFS path that corresponds to the DFS root or the DFS link for which target information is returned.
319    #[br(seek_before = _start.seek_from((dfs_alternate_path_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
320    #[bw(write_with = PosMarker::write_roff_b_plus, args(&dfs_alternate_path_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
321    pub dfs_alternate_path: NullWideString,
322    /// The DFS target that corresponds to this entry.
323    #[br(seek_before = _start.seek_from((network_address_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
324    #[bw(write_with = PosMarker::write_roff_b_plus, args(&network_address_offset, &_start, ReferralEntry::COMMON_PART_SIZE as u64))]
325    pub network_address: NullWideString,
326
327    #[br(seek_before = _restore_position.seek_from(0))]
328    #[bw(calc = ())]
329    __: (),
330}
331
332/// 2.2.5.3.2 NameListReferral Flag Set to 1
333#[binrw::binrw]
334#[derive(Debug, PartialEq, Eq)]
335pub struct EntryV3DCRefs {
336    #[bw(calc = PosMarker::default())]
337    _start: PosMarker<()>,
338    #[br(assert(special_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
339    #[bw(calc = PosMarker::default())]
340    special_name_offset: PosMarker<u16>,
341    number_of_expanded_names: u16,
342    #[br(assert(expanded_name_offset.value >= EntryV3Value::OFFSET_FROM_ENTRY_START))]
343    #[bw(calc = PosMarker::default())]
344    expanded_name_offset: PosMarker<u16>,
345
346    #[bw(calc = PosMarker::default())]
347    _restore_position: PosMarker<()>,
348
349    #[br(seek_before = _start.seek_from((special_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
350    pub special_name: NullWideString,
351    #[br(seek_before = _start.seek_from((expanded_name_offset.value - EntryV3Value::OFFSET_FROM_ENTRY_START).into()))]
352    #[br(count = number_of_expanded_names)]
353    pub expanded_names: Vec<NullWideString>,
354
355    #[br(seek_before = _restore_position.seek_from(0))]
356    #[bw(calc = ())]
357    __: (),
358}
359
360#[binrw::binrw]
361#[derive(Debug, PartialEq, Eq)]
362pub struct ReferralEntryValueV4 {
363    /// Type of server hosting the target
364    pub server_type: DfsServerType,
365    // The ONLY valid flag is TargetSetBoundary.
366    #[br(assert((referral_entry_flags & !u16::from_le_bytes(ReferralEntryFlagsV4::new().with_target_set_boundary(true).into_bytes())) == 0))]
367    pub referral_entry_flags: u16,
368    /// The time-out value, in seconds, of the DFS root or DFS link.
369    pub time_to_live: u32,
370    // name_list_referral: bool is ALWAYS 0, so we know the type of the value.
371    pub refs: EntryV3V4DfsPaths,
372}
373
374/// Internal.
375#[bitfield]
376#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
377#[bw(map = |&x| Self::into_bytes(x))]
378#[br(map = Self::from_bytes)]
379struct ReferralEntryFlagsV4 {
380    #[skip]
381    __: B2,
382    #[skip(getters)]
383    target_set_boundary: bool,
384    #[skip]
385    __: B13,
386}
387
388#[cfg(test)]
389mod tests {
390    use super::*;
391    use smb_tests::*;
392
393    test_binrw! {
394        struct ReqGetDfsReferral {
395            max_referral_level: ReferralLevel::V4,
396            request_file_name: r"\ADC.aviv.local\dfs\Docs".into(),
397        } => "04005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f00630073000000"
398    }
399
400    test_binrw_read! {
401        struct RespGetDfsReferral {
402            path_consumed: 48,
403            referral_header_flags: ReferralHeaderFlags::new().with_storage_servers(true),
404            referral_entries: vec![
405                ReferralEntry {
406                    value: ReferralEntryValue::V4(ReferralEntryValueV4 {
407                        server_type: DfsServerType::NonRoot,
408                        referral_entry_flags: u16::from_le_bytes(
409                            ReferralEntryFlagsV4::new()
410                                .with_target_set_boundary(true)
411                                .into_bytes()
412                        ),
413                        time_to_live: 1800,
414                        refs: EntryV3V4DfsPaths {
415                            dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
416                            dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
417                            network_address: r"\ADC\Shares\Docs".into()
418                        }
419                    })
420                },
421                ReferralEntry {
422                    value: ReferralEntryValue::V4(ReferralEntryValueV4 {
423                        server_type: DfsServerType::NonRoot,
424                        referral_entry_flags: 0,
425                        time_to_live: 1800,
426                        refs: EntryV3V4DfsPaths {
427                            dfs_path: r"\ADC.aviv.local\dfs\Docs".into(),
428                            dfs_alternate_path: r"\ADC.aviv.local\dfs\Docs".into(),
429                            network_address: r"\FSRV\Shares\MyShare".into()
430                        }
431                    })
432                }
433            ],
434        } => "300002000200000004002200000004000807000044007600a8000000000000000000000000000000000004002200000000000807000022005400a
435        800000000000000000000000000000000005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f00
436        6300730000005c004100440043002e0061007600690076002e006c006f00630061006c005c006400660073005c0044006f006300730000005c004100440
437        043005c005300680061007200650073005c0044006f006300730000005c0046005300520056005c005300680061007200650073005c004d007900530068
438        006100720065000000"
439    }
440}