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), parse_with = binrw::helpers::until_eof)]
134 #[bw(if(get_quota_info_content.is_some()))]
135 #[bw(write_with = ChainedItem::write_chained_size_opt, args(&sid_list_length))]
136 pub get_quota_info_content: Option<Vec<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
148#[binrw::binrw]
149#[derive(Debug)]
150pub struct GetEaInfoList {
151 #[br(parse_with = binrw::helpers::until_eof)]
152 #[bw(write_with = FileGetEaInformation::write_chained)]
153 pub values: Vec<FileGetEaInformation>,
154}
155
156#[binrw::binrw]
157#[derive(Debug, PartialEq, Eq)]
158pub struct QueryInfoResponse {
159 #[bw(calc = 9)]
160 #[br(assert(_structure_size == 9))]
161 _structure_size: u16,
162 #[bw(calc = PosMarker::default())]
163 output_buffer_offset: PosMarker<u16>,
164 #[bw(calc = PosMarker::default())]
165 output_buffer_length: PosMarker<u32>,
166 #[br(seek_before = SeekFrom::Start(output_buffer_offset.value.into()))]
167 #[br(map_stream = |s| s.take_seek(output_buffer_length.value.into()))]
168 #[bw(write_with = PosMarker::write_aoff_size, args(&output_buffer_offset, &output_buffer_length))]
169 data: QueryInfoResponseData,
170}
171
172impl QueryInfoResponse {
173 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
178 self.data.parse(info_type)
179 }
180}
181
182#[binrw::binrw]
185#[derive(Debug, PartialEq, Eq)]
186pub struct QueryInfoResponseData {
187 #[br(parse_with = binrw::helpers::until_eof)]
188 data: Vec<u8>,
189}
190
191impl QueryInfoResponseData {
192 pub fn parse(&self, info_type: InfoType) -> Result<QueryInfoData, binrw::Error> {
193 let mut cursor = Cursor::new(&self.data);
194 QueryInfoData::read_args(&mut cursor, (info_type,))
195 }
196}
197
198impl From<Vec<u8>> for QueryInfoResponseData {
199 fn from(data: Vec<u8>) -> Self {
200 QueryInfoResponseData { data }
201 }
202}
203
204query_info_data! {
205 QueryInfoData
206 File: RawQueryInfoData<QueryFileInfo>,
207 FileSystem: RawQueryInfoData<QueryFileSystemInfo>,
208 Security: SecurityDescriptor,
209 Quota: QueryQuotaInfo,
210}
211
212#[cfg(test)]
213mod tests {
214
215 use time::macros::datetime;
216
217 use crate::*;
218 use smb_dtyp::*;
219
220 use super::*;
221
222 #[test]
223 pub fn test_query_info_req_short_write() {
224 let data = encode_content(
225 QueryInfoRequest {
226 info_type: InfoType::File,
227 info_class: QueryInfoClass::File(QueryFileInfoClass::NetworkOpenInformation),
228 output_buffer_length: 56,
229 additional_info: AdditionalInfo::new(),
230 flags: QueryInfoFlags::new(),
231 file_id: [
232 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0,
233 0x0,
234 ]
235 .into(),
236 data: GetInfoRequestData::None(()),
237 }
238 .into(),
239 );
240 assert_eq!(
241 data,
242 [
243 0x29, 0x0, 0x1, 0x22, 0x38, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
244 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x77, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
245 0xc5, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0
246 ]
247 )
248 }
249
250 #[test]
251 pub fn test_query_info_ea_request() {
252 let req = QueryInfoRequest {
253 info_type: InfoType::File,
254 info_class: QueryInfoClass::File(QueryFileInfoClass::FullEaInformation),
255 additional_info: AdditionalInfo::new(),
256 flags: QueryInfoFlags::new()
257 .with_restart_scan(true)
258 .with_return_single_entry(true),
259 file_id: [
260 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0,
261 ]
262 .into(),
263 data: GetInfoRequestData::EaInfo(GetEaInfoList {
264 values: vec![
265 FileGetEaInformationInner {
266 ea_name: "$MpEa_D262AC624451295".into(),
267 }
268 .into(),
269 ],
270 }),
271 output_buffer_length: 554,
272 };
273 let content_data = encode_content(req.into());
274 assert_eq!(
275 content_data,
276 [
277 0x29, 0x0, 0x1, 0xf, 0x2a, 0x2, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x1b, 0x0, 0x0, 0x0,
278 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x7a, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
279 0xd1, 0x0, 0x10, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x15, 0x24, 0x4d,
280 0x70, 0x45, 0x61, 0x5f, 0x44, 0x32, 0x36, 0x32, 0x41, 0x43, 0x36, 0x32, 0x34, 0x34,
281 0x35, 0x31, 0x32, 0x39, 0x35, 0x0
282 ]
283 )
284 }
285
286 #[test]
287 pub fn test_query_security_request() {
288 let res = encode_content(
289 QueryInfoRequest {
290 info_type: InfoType::Security,
291 info_class: Default::default(),
292 output_buffer_length: 0,
293 additional_info: AdditionalInfo::new()
294 .with_owner_security_information(true)
295 .with_group_security_information(true)
296 .with_dacl_security_information(true)
297 .with_sacl_security_information(true),
298 flags: QueryInfoFlags::new(),
299 file_id: guid!("0000002b-000d-0000-3100-00000d000000").into(),
300 data: GetInfoRequestData::None(()),
301 }
302 .into(),
303 );
304 assert_eq!(
305 res,
306 &[
307 0x29, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x68, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
308 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2b, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0,
309 0x31, 0x0, 0x0, 0x0, 0xd, 0x0, 0x0, 0x0,
310 ]
311 );
312 }
313
314 #[test]
315 pub fn test_query_info_resp_parse_basic() {
316 let parsed = decode_content(&[
317 0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x1, 0x0,
318 0x19, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
319 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x69, 0x0, 0x0, 0x28, 0x0, 0x30, 0x0, 0x0, 0x77,
320 0x53, 0x6f, 0xb5, 0x9c, 0x21, 0xa4, 0xcd, 0x99, 0x9b, 0xc0, 0x87, 0xb9, 0x6, 0x83,
321 0xa3, 0x9, 0x0, 0x48, 0x0, 0x28, 0x0, 0x0, 0x0, 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58,
322 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51,
323 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0,
324 0x0, 0x0, 0x0,
325 ]);
326 let parsed = parsed.content.to_queryinfo().unwrap();
327 assert_eq!(
328 parsed,
329 QueryInfoResponse {
330 data: [
331 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51,
332 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1,
333 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
334 ]
335 .to_vec()
336 .into(),
337 }
338 );
339 }
340
341 #[test]
342 pub fn test_query_info_resp_parse_file() {
343 let raw_data: QueryInfoResponseData = [
344 0x5b, 0x6c, 0x44, 0xce, 0x6a, 0x58, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb,
345 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b, 0xdb, 0x1, 0x4, 0x8f, 0xa1, 0xd, 0x51, 0x6b,
346 0xdb, 0x1, 0x20, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
347 ]
348 .to_vec()
349 .into();
350 assert_eq!(
351 raw_data
352 .parse(InfoType::File)
353 .unwrap()
354 .unwrap_file()
355 .parse(QueryFileInfoClass::BasicInformation)
356 .unwrap(),
357 QueryFileInfo::BasicInformation(FileBasicInformation {
358 creation_time: datetime!(2024-12-27 14:22:48.792994700).into(),
359 last_access_time: datetime!(2025-01-20 15:36:20.277632400).into(),
360 last_write_time: datetime!(2025-01-20 15:36:20.277632400).into(),
361 change_time: datetime!(2025-01-20 15:36:20.277632400).into(),
362 file_attributes: FileAttributes::new().with_archive(true)
363 })
364 )
365 }
366
367 #[test]
368 fn test_query_info_resp_parse_stream_info() {
369 let raw_data: QueryInfoResponseData = [
370 0x48, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x93, 0x00, 0x00, 0x00, 0x00, 0x00,
371 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x5a, 0x00,
372 0x6f, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x49, 0x00, 0x64, 0x00, 0x65, 0x00,
373 0x6e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00, 0x65, 0x00, 0x72, 0x00,
374 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00, 0x00, 0x00,
375 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0xd1, 0xd6, 0x00, 0x00,
376 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00,
377 0x3a, 0x00, 0x24, 0x00, 0x44, 0x00, 0x41, 0x00, 0x54, 0x00, 0x41, 0x00,
378 ]
379 .to_vec()
380 .into();
381
382 assert_eq!(
383 raw_data
384 .parse(InfoType::File)
385 .unwrap()
386 .unwrap_file()
387 .parse(QueryFileInfoClass::StreamInformation)
388 .unwrap(),
389 QueryFileInfo::StreamInformation(
390 vec![
391 FileStreamInformationInner {
392 stream_size: 0x93,
393 stream_allocation_size: 0x1000,
394 stream_name: SizedWideString::from(":Zone.Identifier:$DATA"),
395 },
396 FileStreamInformationInner {
397 stream_size: 0xd6d1,
398 stream_allocation_size: 0xd000,
399 stream_name: SizedWideString::from("::$DATA"),
400 },
401 ]
402 .into()
403 )
404 )
405 }
406}