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