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)]
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 #[br(assert(_reserved == 0))]
27 #[bw(calc = 0)]
28 _reserved: u16,
29 #[bw(calc = PosMarker::default())]
30 input_buffer_length: PosMarker<u32>,
31 pub additional_info: AdditionalInfo,
32 pub flags: QueryInfoFlags,
33 pub file_id: FileId,
34 #[br(map_stream = |s| s.take_seek(input_buffer_length.value as u64))]
35 #[br(args(&info_class, info_type))]
36 #[bw(write_with = PosMarker::write_aoff_size_a, args(&_input_buffer_offset, &input_buffer_length, (info_class, *info_type)))]
37 pub data: GetInfoRequestData,
38}
39
40#[binrw::binrw]
41#[derive(Debug, PartialEq, Eq)]
42#[br(import(info_type: InfoType))]
43#[bw(import(info_type: &InfoType))]
44pub enum QueryInfoClass {
45 #[br(pre_assert(matches!(info_type, InfoType::File)))]
46 #[bw(assert(matches!(info_type, InfoType::File)))]
47 File(QueryFileInfoClass),
48
49 #[br(pre_assert(matches!(info_type, InfoType::FileSystem)))]
50 #[bw(assert(matches!(info_type, InfoType::FileSystem)))]
51 FileSystem(QueryFileSystemInfoClass),
52
53 Empty(NullByte),
54}
55
56impl Default for QueryInfoClass {
57 fn default() -> Self {
58 QueryInfoClass::Empty(NullByte {})
59 }
60}
61
62#[binrw::binrw]
63#[derive(Debug, PartialEq, Eq, Default)]
64pub struct NullByte {
65 #[bw(calc = 0)]
66 #[br(assert(_null == 0))]
67 _null: u8,
68}
69
70impl AdditionalInfo {
71 pub fn is_security(&self) -> bool {
72 self.owner_security_information()
73 || self.group_security_information()
74 || self.dacl_security_information()
75 || self.sacl_security_information()
76 || self.label_security_information()
77 || self.attribute_security_information()
78 || self.scope_security_information()
79 || self.backup_security_information()
80 }
81}
82
83#[bitfield]
84#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
85#[bw(map = |&x| Self::into_bytes(x))]
86#[br(map = Self::from_bytes)]
87pub struct QueryInfoFlags {
88 pub restart_scan: bool,
89 pub return_single_entry: bool,
90 pub index_specified: bool,
91 #[skip]
92 __: B29,
93}
94
95#[binrw::binrw]
99#[derive(Debug)]
100#[brw(import(file_info_class: &QueryInfoClass, query_info_type: InfoType))]
101pub enum GetInfoRequestData {
102 #[br(pre_assert(query_info_type == InfoType::Quota))]
104 #[bw(assert(query_info_type == InfoType::Quota))]
105 Quota(QueryQuotaInfo),
106
107 #[br(pre_assert(matches!(file_info_class, QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)) && query_info_type == InfoType::File))]
109 #[bw(assert(matches!(file_info_class, QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)) && query_info_type == InfoType::File))]
110 EaInfo(GetEaInfoList),
111
112 #[br(pre_assert(query_info_type != InfoType::Quota && !(query_info_type == InfoType::File && matches!(file_info_class , QueryInfoClass::File(QueryFileInfoClass::FullEaInformation)))))]
114 None(()),
115}
116
117#[binrw::binrw]
118#[derive(Debug)]
119pub struct QueryQuotaInfo {
120 pub return_single: Boolean,
121 pub restart_scan: Boolean,
122 #[bw(calc = 0)]
123 _reserved: u16,
124 #[bw(calc = PosMarker::default())]
125 sid_list_length: PosMarker<u32>, #[bw(calc = PosMarker::default())]
127 start_sid_length: PosMarker<u32>, #[bw(calc = PosMarker::default())]
129 start_sid_offset: PosMarker<u32>,
130
131 #[br(if(sid_list_length.value > 0))]
133 #[br(map_stream = |s| s.take_seek(sid_list_length.value as u64))]
134 #[bw(if(get_quota_info_content.as_ref().is_some_and(|v| !v.is_empty())))]
135 #[bw(write_with = PosMarker::write_size, args(&sid_list_length))]
136 pub get_quota_info_content: Option<ChainedItemList<FileGetQuotaInformation>>,
137
138 #[br(if(start_sid_length.value > 0))]
140 #[bw(if(sid.is_some()))]
141 #[br(seek_before = SeekFrom::Current(start_sid_offset.value as i64))]
142 #[bw(write_with = PosMarker::write_size, args(&start_sid_length))]
143 #[brw(assert(get_quota_info_content.is_none() != sid.is_none()))]
144 pub sid: Option<SID>,
146}
147
148impl QueryQuotaInfo {
149 pub fn new(
153 return_single: bool,
154 restart_scan: bool,
155 content: Vec<FileGetQuotaInformation>,
156 ) -> Self {
157 Self {
158 return_single: return_single.into(),
159 restart_scan: restart_scan.into(),
160 get_quota_info_content: Some(content.into()),
161 sid: None,
162 }
163 }
164
165 pub fn new_sid(return_single: bool, restart_scan: bool, sid: SID) -> Self {
169 Self {
170 return_single: return_single.into(),
171 restart_scan: restart_scan.into(),
172 get_quota_info_content: None,
173 sid: Some(sid),
174 }
175 }
176}
177
178#[derive(BinRead, BinWrite, Debug)]
179pub struct GetEaInfoList {
180 pub values: ChainedItemList<FileGetEaInformation>,
181}
182
183#[binrw::binrw]
184#[derive(Debug, PartialEq, Eq)]
185pub struct QueryInfoResponse {
186 #[bw(calc = 9)]
187 #[br(assert(_structure_size == 9))]
188 _structure_size: u16,
189 #[bw(calc = PosMarker::default())]
190 output_buffer_offset: PosMarker<u16>,
191 #[bw(calc = PosMarker::default())]
192 output_buffer_length: PosMarker<u32>,
193 #[br(seek_before = SeekFrom::Start(output_buffer_offset.value.into()))]
194 #[br(map_stream = |s| s.take_seek(output_buffer_length.value.into()))]
195 #[bw(write_with = PosMarker::write_aoff_size, args(&output_buffer_offset, &output_buffer_length))]
196 data: QueryInfoResponseData,
197}
198
199impl QueryInfoResponse {
200 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
205 self.data.parse(info_type)
206 }
207}
208
209#[binrw::binrw]
212#[derive(Debug, PartialEq, Eq)]
213pub struct QueryInfoResponseData {
214 #[br(parse_with = binrw::helpers::until_eof)]
215 data: Vec<u8>,
216}
217
218impl QueryInfoResponseData {
219 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
220 let mut cursor = Cursor::new(&self.data);
221 QueryInfoData::read_args(&mut cursor, (info_type,))
222 }
223}
224
225impl From<Vec<u8>> for QueryInfoResponseData {
226 fn from(data: Vec<u8>) -> Self {
227 QueryInfoResponseData { data }
228 }
229}
230
231query_info_data! {
232 QueryInfoData
233 File: RawQueryInfoData<QueryFileInfo>,
234 FileSystem: RawQueryInfoData<QueryFileSystemInfo>,
235 Security: SecurityDescriptor,
236 Quota: ChainedItemList<FileQuotaInformation>,
237}
238
239#[cfg(test)]
240mod tests {
241
242 use time::macros::datetime;
243
244 use crate::*;
245 use smb_dtyp::*;
246
247 use super::*;
248
249 #[test]
250 pub fn test_query_info_req_short_write() {
251 let data = encode_content(
252 QueryInfoRequest {
253 info_type: InfoType::File,
254 info_class: QueryInfoClass::File(QueryFileInfoClass::NetworkOpenInformation),
255 output_buffer_length: 56,
256 additional_info: AdditionalInfo::new(),
257 flags: QueryInfoFlags::new(),
258 file_id: [
259 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0,
260 0x0,
261 ]
262 .into(),
263 data: GetInfoRequestData::None(()),
264 }
265 .into(),
266 );
267 assert_eq!(
268 data,
269 [
270 0x29, 0x0, 0x1, 0x22, 0x38, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
271 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
272 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0
273 ]
274 )
275 }
276
277 #[test]
278 pub fn test_query_info_ea_request() {
279 let req = QueryInfoRequest {
280 info_type: InfoType::File,
281 info_class: QueryInfoClass::File(QueryFileInfoClass::FullEaInformation),
282 additional_info: AdditionalInfo::new(),
283 flags: QueryInfoFlags::new()
284 .with_restart_scan(true)
285 .with_return_single_entry(true),
286 file_id: [
287 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0,
288 ]
289 .into(),
290 data: GetInfoRequestData::EaInfo(GetEaInfoList {
291 values: vec![FileGetEaInformation::new("$MpEa_D262AC624451295")].into(),
292 }),
293 output_buffer_length: 554,
294 };
295 let content_data = encode_content(req.into());
296 assert_eq!(
297 content_data,
298 [
299 0x29, 0x0, 0x1, 0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0,
300 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
301 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d,
302 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32, 0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34,
303 0x35, 0x31, 0x32, 0x39, 0x35, 0x0
304 ]
305 )
306 }
307
308 #[test]
309 pub fn test_query_security_request() {
310 let res = encode_content(
311 QueryInfoRequest {
312 info_type: InfoType::Security,
313 info_class: Default::default(),
314 output_buffer_length: 0,
315 additional_info: AdditionalInfo::new()
316 .with_owner_security_information(true)
317 .with_group_security_information(true)
318 .with_dacl_security_information(true)
319 .with_sacl_security_information(true),
320 flags: QueryInfoFlags::new(),
321 file_id: make_guid!("0000002b-000d-0000-3100-00000d000000").into(),
322 data: GetInfoRequestData::None(()),
323 }
324 .into(),
325 );
326 assert_eq!(
327 res,
328 &[
329 0x29, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
330 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0,
331 0x31, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0,
332 ]
333 );
334 }
335
336 #[test]
337 pub fn test_query_info_resp_parse_basic() {
338 let parsed = decode_content(&[
339 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x1, 0x0,
340 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
341 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x69, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x77,
342 0x53, 0x6f, 0xb5, 0x9c, 0x21, 0xa4, 0xcd, 0x99, 0x9b, 0xc0, 0x87, 0xb9, 0x6, 0x83,
343 0xa3, 0x9, 0x0, 0x48, 0x0, 0x28, 0x0, 0x0, 0x0, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58,
344 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51,
345 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0,
346 0x0, 0x0, 0x0,
347 ]);
348 let parsed = parsed.content.to_queryinfo().unwrap();
349 assert_eq!(
350 parsed,
351 QueryInfoResponse {
352 data: [
353 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51,
354 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1,
355 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
356 ]
357 .to_vec()
358 .into(),
359 }
360 );
361 }
362
363 #[test]
364 pub fn test_query_info_resp_parse_file() {
365 let raw_data: QueryInfoResponseData = [
366 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb,
367 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b,
368 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
369 ]
370 .to_vec()
371 .into();
372 assert_eq!(
373 raw_data
374 .parse(InfoType::File)
375 .unwrap()
376 .as_file()
377 .unwrap()
378 .parse(QueryFileInfoClass::BasicInformation)
379 .unwrap(),
380 QueryFileInfo::BasicInformation(FileBasicInformation {
381 creation_time: datetime!(2024-12-27 14:22:48.792994700).into(),
382 last_access_time: datetime!(2025-01-20 15:36:20.277632400).into(),
383 last_write_time: datetime!(2025-01-20 15:36:20.277632400).into(),
384 change_time: datetime!(2025-01-20 15:36:20.277632400).into(),
385 file_attributes: FileAttributes::new().with_archive(true)
386 })
387 )
388 }
389
390 #[test]
391 fn test_query_info_resp_parse_stream_info() {
392 let raw_data: QueryInfoResponseData = [
393 0x48, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
394 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x5a, 0x00,
395 0x6f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x49, 0x00, 0x64, 0x00, 0x65, 0x00,
396 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x72, 0x00,
397 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00, 0x00, 0x00,
398 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd1, 0xd6, 0x00, 0x00,
399 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00,
400 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00,
401 ]
402 .to_vec()
403 .into();
404
405 assert_eq!(
406 raw_data
407 .parse(InfoType::File)
408 .unwrap()
409 .as_file()
410 .unwrap()
411 .parse(QueryFileInfoClass::StreamInformation)
412 .unwrap(),
413 QueryFileInfo::StreamInformation(
414 vec![
415 FileStreamInformationInner {
416 stream_size: 0x93,
417 stream_allocation_size: 0x1000,
418 stream_name: SizedWideString::from(":Zone.Identifier:$DATA"),
419 },
420 FileStreamInformationInner {
421 stream_size: 0xd6d1,
422 stream_allocation_size: 0xd000,
423 stream_name: SizedWideString::from("::$DATA"),
424 },
425 ]
426 .into()
427 )
428 )
429 }
430}