1use crate::FileId;
4use crate::query_info_data;
5use binrw::{io::TakeSeekExt, prelude::*};
6use modular_bitfield::prelude::*;
7use smb_dtyp::{SID, SecurityDescriptor, binrw_util::prelude::*};
8use std::io::{Cursor, SeekFrom};
9
10use super::common::*;
11use smb_fscc::*;
12
13#[binrw::binrw]
14#[derive(Debug, PartialEq, Eq)]
15pub struct QueryInfoRequest {
16 #[bw(calc = 41)]
17 #[br(assert(_structure_size == 41))]
18 _structure_size: u16,
19 pub info_type: InfoType,
20 #[brw(args(info_type))]
21 pub info_class: QueryInfoClass,
22
23 pub output_buffer_length: u32,
24 #[bw(calc = PosMarker::default())]
25 _input_buffer_offset: PosMarker<u16>,
26 #[bw(calc = 0)]
27 _reserved: u16,
28 #[bw(calc = PosMarker::default())]
29 input_buffer_length: PosMarker<u32>,
30 pub additional_info: AdditionalInfo,
31 pub flags: QueryInfoFlags,
32 pub file_id: FileId,
33 #[br(map_stream = |s| s.take_seek(input_buffer_length.value as u64))]
34 #[br(args(&info_class, info_type))]
35 #[bw(write_with = PosMarker::write_aoff_size_a, args(&_input_buffer_offset, &input_buffer_length, (info_class, *info_type)))]
36 pub data: GetInfoRequestData,
37}
38
39#[binrw::binrw]
40#[derive(Debug, PartialEq, Eq)]
41#[br(import(info_type: InfoType))]
42#[bw(import(info_type: &InfoType))]
43pub enum QueryInfoClass {
44 #[br(pre_assert(matches!(info_type, InfoType::File)))]
45 #[bw(assert(matches!(info_type, InfoType::File)))]
46 File(QueryFileInfoClass),
47
48 #[br(pre_assert(matches!(info_type, InfoType::FileSystem)))]
49 #[bw(assert(matches!(info_type, InfoType::FileSystem)))]
50 FileSystem(QueryFileSystemInfoClass),
51
52 Empty(NullByte),
53}
54
55impl Default for QueryInfoClass {
56 fn default() -> Self {
57 QueryInfoClass::Empty(NullByte {})
58 }
59}
60
61#[binrw::binrw]
62#[derive(Debug, PartialEq, Eq, Default)]
63pub struct NullByte {
64 #[bw(calc = 0)]
65 #[br(assert(_null == 0))]
66 _null: u8,
67}
68
69impl AdditionalInfo {
70 pub fn is_security(&self) -> bool {
71 self.owner_security_information()
72 || self.group_security_information()
73 || self.dacl_security_information()
74 || self.sacl_security_information()
75 || self.label_security_information()
76 || self.attribute_security_information()
77 || self.scope_security_information()
78 || self.backup_security_information()
79 }
80}
81
82#[bitfield]
83#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
84#[bw(map = |&x| Self::into_bytes(x))]
85#[br(map = Self::from_bytes)]
86pub struct QueryInfoFlags {
87 pub restart_scan: bool,
88 pub return_single_entry: bool,
89 pub index_specified: bool,
90 #[skip]
91 __: B29,
92}
93
94#[binrw::binrw]
98#[derive(Debug, PartialEq, Eq)]
99#[brw(import(file_info_class: &QueryInfoClass, query_info_type: InfoType))]
100pub enum GetInfoRequestData {
101 #[br(pre_assert(query_info_type == InfoType::Quota))]
103 #[bw(assert(query_info_type == InfoType::Quota))]
104 Quota(QueryQuotaInfo),
105
106 #[br(pre_assert(matches!(file_info_class, QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)) && query_info_type == InfoType::File))]
108 #[bw(assert(matches!(file_info_class, QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)) && query_info_type == InfoType::File))]
109 EaInfo(GetEaInfoList),
110
111 #[br(pre_assert(query_info_type != InfoType::Quota && !(query_info_type == InfoType::File && matches!(file_info_class , QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)))))]
113 None(()),
114}
115
116#[binrw::binrw]
117#[derive(Debug, PartialEq, Eq)]
118pub struct QueryQuotaInfo {
119 pub return_single: Boolean,
120 pub restart_scan: Boolean,
121 #[bw(calc = 0)]
122 _reserved: u16,
123 #[bw(calc = PosMarker::default())]
124 sid_list_length: PosMarker<u32>, #[bw(calc = PosMarker::default())]
126 start_sid_length: PosMarker<u32>, #[bw(calc = PosMarker::default())]
128 start_sid_offset: PosMarker<u32>,
129
130 #[br(if(sid_list_length.value > 0))]
132 #[br(map_stream = |s| s.take_seek(sid_list_length.value as u64))]
133 #[bw(if(get_quota_info_content.as_ref().is_some_and(|v| !v.is_empty())))]
134 #[bw(write_with = PosMarker::write_size, args(&sid_list_length))]
135 pub get_quota_info_content: Option<ChainedItemList<FileGetQuotaInformation>>,
136
137 #[br(if(start_sid_length.value > 0))]
139 #[bw(if(sid.is_some()))]
140 #[br(seek_before = SeekFrom::Current(start_sid_offset.value as i64))]
141 #[bw(write_with = PosMarker::write_size, args(&start_sid_length))]
142 #[brw(assert(get_quota_info_content.is_none() != sid.is_none()))]
143 pub sid: Option<SID>,
145}
146
147impl QueryQuotaInfo {
148 pub fn new(
152 return_single: bool,
153 restart_scan: bool,
154 content: Vec<FileGetQuotaInformation>,
155 ) -> Self {
156 Self {
157 return_single: return_single.into(),
158 restart_scan: restart_scan.into(),
159 get_quota_info_content: Some(content.into()),
160 sid: None,
161 }
162 }
163
164 pub fn new_sid(return_single: bool, restart_scan: bool, sid: SID) -> Self {
168 Self {
169 return_single: return_single.into(),
170 restart_scan: restart_scan.into(),
171 get_quota_info_content: None,
172 sid: Some(sid),
173 }
174 }
175}
176
177#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
178pub struct GetEaInfoList {
179 pub values: ChainedItemList<FileGetEaInformation>,
180}
181
182#[binrw::binrw]
183#[derive(Debug, PartialEq, Eq)]
184pub struct QueryInfoResponse {
185 #[bw(calc = 9)]
186 #[br(assert(_structure_size == 9))]
187 _structure_size: u16,
188 #[bw(calc = PosMarker::default())]
189 output_buffer_offset: PosMarker<u16>,
190 #[bw(calc = PosMarker::default())]
191 output_buffer_length: PosMarker<u32>,
192 #[br(seek_before = SeekFrom::Start(output_buffer_offset.value.into()))]
193 #[br(map_stream = |s| s.take_seek(output_buffer_length.value.into()))]
194 #[bw(write_with = PosMarker::write_aoff_size, args(&output_buffer_offset, &output_buffer_length))]
195 data: QueryInfoResponseData,
196}
197
198impl QueryInfoResponse {
199 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
204 self.data.parse(info_type)
205 }
206}
207
208#[binrw::binrw]
211#[derive(Debug, PartialEq, Eq)]
212pub struct QueryInfoResponseData {
213 #[br(parse_with = binrw::helpers::until_eof)]
214 data: Vec<u8>,
215}
216
217impl QueryInfoResponseData {
218 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
219 let mut cursor = Cursor::new(&self.data);
220 QueryInfoData::read_args(&mut cursor, (info_type,))
221 }
222}
223
224impl From<Vec<u8>> for QueryInfoResponseData {
225 fn from(data: Vec<u8>) -> Self {
226 QueryInfoResponseData { data }
227 }
228}
229
230query_info_data! {
231 QueryInfoData
232 File: RawQueryInfoData<QueryFileInfo>,
233 FileSystem: RawQueryInfoData<QueryFileSystemInfo>,
234 Security: SecurityDescriptor,
235 Quota: ChainedItemList<FileQuotaInformation>,
236}
237
238#[cfg(test)]
239mod tests {
240
241 use time::macros::datetime;
242
243 use crate::*;
244 use smb_dtyp::*;
245
246 use super::*;
247
248 const QUERY_INFO_HEADER_DATA: &'static str = "";
249
250 test_request! {
251 query_info_basic: QueryInfo {
252 info_type: InfoType::File,
253 info_class: QueryInfoClass::File(QueryFileInfoClass::NetworkOpenInformation),
254 output_buffer_length: 56,
255 additional_info: AdditionalInfo::new(),
256 flags: QueryInfoFlags::new(),
257 file_id: [
258 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0,
259 0x0,
260 ]
261 .into(),
262 data: GetInfoRequestData::None(()),
263 } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "290001223800000068000000000000000000000000000000770500000c000000c50010000c000000")
264 }
265
266 test_request! {
267 query_info_get_ea: QueryInfo {
268 info_type: InfoType::File,
269 info_class: QueryInfoClass::File(QueryFileInfoClass::FullEaInformation),
270 additional_info: AdditionalInfo::new(),
271 flags: QueryInfoFlags::new()
272 .with_restart_scan(true)
273 .with_return_single_entry(true),
274 file_id: [
275 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0,
276 ]
277 .into(),
278 data: GetInfoRequestData::EaInfo(GetEaInfoList {
279 values: vec![FileGetEaInformation::new("$MpEa_D262AC624451295")].into(),
280 }),
281 output_buffer_length: 554,
282 } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "2900010f2a020000680000001b00000000000000030000007a0500000c000000d10010000c0000000000000015244d7045615f44323632414336323434353132393500")
283 }
284
285 test_request! {
286 query_security: QueryInfo {
287 info_type: InfoType::Security,
288 info_class: Default::default(),
289 output_buffer_length: 0,
290 additional_info: AdditionalInfo::new()
291 .with_owner_security_information(true)
292 .with_group_security_information(true)
293 .with_dacl_security_information(true)
294 .with_sacl_security_information(true),
295 flags: QueryInfoFlags::new(),
296 file_id: make_guid!("0000002b-000d-0000-3100-00000d000000").into(),
297 data: GetInfoRequestData::None(()),
298 } => const_format::concatcp!(QUERY_INFO_HEADER_DATA, "290003000000000068000000000000000f000000000000002b0000000d000000310000000d000000")
299 }
300
301 test_response! {
302 QueryInfo {
303 data: [
304 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51,
305 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1,
306 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
307 ]
308 .to_vec()
309 .into()
310 } => "09004800280000005b6c44ce6a58db01048fa10d516bdb01048fa10d516bdb01048fa10d516bdb012000000000000000"
311 }
312
313 #[test]
314 pub fn test_query_info_resp_parse_file() {
315 let raw_data: QueryInfoResponseData = [
316 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb,
317 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b,
318 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
319 ]
320 .to_vec()
321 .into();
322 assert_eq!(
323 raw_data
324 .parse(InfoType::File)
325 .unwrap()
326 .as_file()
327 .unwrap()
328 .parse(QueryFileInfoClass::BasicInformation)
329 .unwrap(),
330 QueryFileInfo::BasicInformation(FileBasicInformation {
331 creation_time: datetime!(2024-12-27 14:22:48.792994700).into(),
332 last_access_time: datetime!(2025-01-20 15:36:20.277632400).into(),
333 last_write_time: datetime!(2025-01-20 15:36:20.277632400).into(),
334 change_time: datetime!(2025-01-20 15:36:20.277632400).into(),
335 file_attributes: FileAttributes::new().with_archive(true)
336 })
337 )
338 }
339
340 #[test]
341 fn test_query_info_resp_parse_stream_info() {
342 let raw_data: QueryInfoResponseData = [
343 0x48, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
344 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x5a, 0x00,
345 0x6f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x49, 0x00, 0x64, 0x00, 0x65, 0x00,
346 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x72, 0x00,
347 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00, 0x00, 0x00,
348 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd1, 0xd6, 0x00, 0x00,
349 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00,
350 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00,
351 ]
352 .to_vec()
353 .into();
354
355 assert_eq!(
356 raw_data
357 .parse(InfoType::File)
358 .unwrap()
359 .as_file()
360 .unwrap()
361 .parse(QueryFileInfoClass::StreamInformation)
362 .unwrap(),
363 QueryFileInfo::StreamInformation(
364 vec![
365 FileStreamInformationInner {
366 stream_size: 0x93,
367 stream_allocation_size: 0x1000,
368 stream_name: SizedWideString::from(":Zone.Identifier:$DATA"),
369 },
370 FileStreamInformationInner {
371 stream_size: 0xd6d1,
372 stream_allocation_size: 0xd000,
373 stream_name: SizedWideString::from("::$DATA"),
374 },
375 ]
376 .into()
377 )
378 )
379 }
380}