smb_msg/
notify.rs

1//! SMB2 Change Notify Request and Response, and Server to Client Notification
2use std::io::SeekFrom;
3
4use binrw::io::TakeSeekExt;
5use binrw::prelude::*;
6use modular_bitfield::prelude::*;
7
8use super::FileId;
9use smb_dtyp::binrw_util::prelude::*;
10use smb_fscc::*;
11
12#[binrw::binrw]
13#[derive(Debug)]
14pub struct ChangeNotifyRequest {
15    #[bw(calc = 32)]
16    #[br(assert(_structure_size == 32))]
17    _structure_size: u16,
18    pub flags: NotifyFlags,
19    pub output_buffer_length: u32,
20    pub file_id: FileId,
21    pub completion_filter: NotifyFilter,
22    #[bw(calc = 0)]
23    _reserved: u32,
24}
25
26#[bitfield]
27#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
28#[bw(map = |&x| Self::into_bytes(x))]
29#[br(map = Self::from_bytes)]
30pub struct NotifyFlags {
31    pub watch_tree: bool,
32    #[skip]
33    __: B15,
34}
35
36#[bitfield]
37#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
38#[bw(map = |&x| Self::into_bytes(x))]
39#[br(map = Self::from_bytes)]
40pub struct NotifyFilter {
41    pub file_name: bool,
42    pub dir_name: bool,
43    pub attributes: bool,
44    pub size: bool,
45
46    pub last_write: bool,
47    pub last_access: bool,
48    pub creation: bool,
49    pub ea: bool,
50
51    pub security: bool,
52    pub stream_name: bool,
53    pub stream_size: bool,
54    pub stream_write: bool,
55
56    #[skip]
57    __: B20,
58}
59
60impl NotifyFilter {
61    pub fn all() -> Self {
62        Self::new()
63            .with_file_name(true)
64            .with_dir_name(true)
65            .with_attributes(true)
66            .with_size(true)
67            .with_last_write(true)
68            .with_last_access(true)
69            .with_creation(true)
70            .with_ea(true)
71            .with_security(true)
72            .with_stream_name(true)
73            .with_stream_size(true)
74            .with_stream_write(true)
75    }
76}
77
78#[binrw::binrw]
79#[derive(Debug, PartialEq, Eq)]
80pub struct ChangeNotifyResponse {
81    #[bw(calc = 9)]
82    #[br(assert(_structure_size == 9))]
83    _structure_size: u16,
84    #[bw(calc = PosMarker::default())]
85    _output_buffer_offset: PosMarker<u16>,
86    #[bw(calc = PosMarker::default())]
87    _output_buffer_length: PosMarker<u32>,
88    #[br(seek_before = SeekFrom::Start(_output_buffer_offset.value.into()))]
89    #[br(map_stream = |s| s.take_seek(_output_buffer_length.value.into()))]
90    pub buffer: ChainedItemList<FileNotifyInformation, 4>,
91}
92
93#[binrw::binrw]
94#[derive(Debug, PartialEq, Eq)]
95pub struct ServerToClientNotification {
96    structure_size: u16,
97    #[bw(calc = 0)]
98    _reserved: u16,
99    #[bw(calc = notification.get_type())]
100    notification_type: NotificationType,
101    #[br(args(notification_type))]
102    pub notification: Notification,
103}
104
105#[binrw::binrw]
106#[derive(Debug, PartialEq, Eq)]
107#[brw(repr(u32))]
108pub enum NotificationType {
109    NotifySessionClosed = 0,
110}
111
112#[binrw::binrw]
113#[derive(Debug, PartialEq, Eq)]
114#[br(import(notification_type: NotificationType))]
115pub enum Notification {
116    #[br(pre_assert(notification_type == NotificationType::NotifySessionClosed))]
117    NotifySessionClosed(NotifySessionClosed),
118}
119
120impl Notification {
121    pub fn get_type(&self) -> NotificationType {
122        match self {
123            Notification::NotifySessionClosed(_) => NotificationType::NotifySessionClosed,
124        }
125    }
126}
127
128#[binrw::binrw]
129#[derive(Debug, PartialEq, Eq)]
130pub struct NotifySessionClosed {
131    #[bw(calc = 0)]
132    _reserved: u32,
133}
134
135#[cfg(test)]
136mod tests {
137    use std::io::Cursor;
138
139    use crate::*;
140    use smb_dtyp::guid::Guid;
141
142    use super::*;
143
144    #[test]
145    pub fn change_notify_request_write() {
146        let request = ChangeNotifyRequest {
147            flags: NotifyFlags::new(),
148            output_buffer_length: 2048,
149            file_id: "000005d1-000c-0000-1900-00000c000000"
150                .parse::<Guid>()
151                .unwrap()
152                .into(),
153            completion_filter: NotifyFilter::new()
154                .with_file_name(true)
155                .with_dir_name(true)
156                .with_attributes(true)
157                .with_last_write(true),
158        };
159
160        let mut cursor = Cursor::new(Vec::new());
161        request.write_le(&mut cursor).unwrap();
162        assert_eq!(
163            cursor.into_inner(),
164            [
165                0x20, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0xd1, 0x5, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0,
166                0x19, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
167            ]
168        );
169    }
170
171    #[test]
172    pub fn test_change_notify_response_pending_parse() {
173        let data = [0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0];
174        let response = ChangeNotifyResponse::read_le(&mut Cursor::new(&data)).unwrap();
175        assert_eq!(
176            response,
177            ChangeNotifyResponse {
178                buffer: Default::default()
179            }
180        );
181    }
182
183    #[test]
184    pub fn test_change_notify_response_with_data_parse() {
185        let data = [
186            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0x0, 0x0, 0x0,
187            0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
188            0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x25, 0x0, 0x0, 0x0, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0,
189            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9, 0x0, 0x48,
190            0x0, 0x34, 0x0, 0x0, 0x0, 0x20, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0,
191            0x4e, 0x0, 0x65, 0x0, 0x77, 0x0, 0x20, 0x0, 0x66, 0x0, 0x6f, 0x0, 0x6c, 0x0, 0x64, 0x0,
192            0x65, 0x0, 0x72, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x6a,
193            0x0, 0x64, 0x0, 0x73, 0x0, 0x61, 0x0,
194        ];
195
196        let parsed = decode_content(&data);
197        let notify_response = parsed.content.to_changenotify().unwrap();
198
199        assert_eq!(
200            notify_response,
201            ChangeNotifyResponse {
202                buffer: vec![
203                    FileNotifyInformation {
204                        action: NotifyAction::RenamedOldName,
205                        file_name: "New folder".into()
206                    },
207                    FileNotifyInformation {
208                        action: NotifyAction::RenamedNewName,
209                        file_name: "jdsa".into()
210                    }
211                ]
212                .into()
213            }
214        );
215    }
216
217    #[test]
218    pub fn test_change_notify_response_azure_files() {
219        let data = [
220            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00,
221            0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
222            0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5d, 0x00,
223            0x00, 0x68, 0x6c, 0x30, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
224            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x48, 0x00, 0x60, 0x01,
225            0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00,
226            0x31, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x78, 0x00, 0x74, 0x00, 0x28, 0x00,
227            0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x65, 0x00,
228            0x72, 0x00, 0x6e, 0x00, 0x65, 0x00, 0x6c, 0x00, 0x2e, 0x00, 0x62, 0x00, 0x69, 0x00,
229            0x6e, 0x00, 0x2e, 0x00, 0x74, 0x00, 0x69, 0x00, 0x6c, 0x00, 0x78, 0x00, 0x00, 0x00,
230            0x01, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x65, 0x00, 0x63, 0x00, 0x32, 0x00,
231            0x2d, 0x00, 0x33, 0x00, 0x2d, 0x00, 0x37, 0x00, 0x30, 0x00, 0x2d, 0x00, 0x32, 0x00,
232            0x32, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x36, 0x00, 0x39, 0x00, 0x2e, 0x00, 0x65, 0x00,
233            0x75, 0x00, 0x2d, 0x00, 0x63, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x72, 0x00,
234            0x61, 0x00, 0x6c, 0x00, 0x2d, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00,
235            0x6d, 0x00, 0x70, 0x00, 0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x61, 0x00,
236            0x6d, 0x00, 0x61, 0x00, 0x7a, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x77, 0x00,
237            0x73, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x72, 0x00,
238            0x64, 0x00, 0x70, 0x00, 0x80, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x6e, 0x00,
239            0x00, 0x00, 0x65, 0x00, 0x63, 0x00, 0x32, 0x00, 0x2d, 0x00, 0x31, 0x00, 0x38, 0x00,
240            0x2d, 0x00, 0x31, 0x00, 0x39, 0x00, 0x38, 0x00, 0x2d, 0x00, 0x35, 0x00, 0x31, 0x00,
241            0x2d, 0x00, 0x39, 0x00, 0x38, 0x00, 0x2e, 0x00, 0x65, 0x00, 0x75, 0x00, 0x2d, 0x00,
242            0x63, 0x00, 0x65, 0x00, 0x6e, 0x00, 0x74, 0x00, 0x72, 0x00, 0x61, 0x00, 0x6c, 0x00,
243            0x2d, 0x00, 0x31, 0x00, 0x2e, 0x00, 0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x70, 0x00,
244            0x75, 0x00, 0x74, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x61, 0x00, 0x6d, 0x00, 0x61, 0x00,
245            0x7a, 0x00, 0x6f, 0x00, 0x6e, 0x00, 0x61, 0x00, 0x77, 0x00, 0x73, 0x00, 0x2e, 0x00,
246            0x63, 0x00, 0x6f, 0x00, 0x6d, 0x00, 0x2e, 0x00, 0x72, 0x00, 0x64, 0x00, 0x70, 0x00,
247            0x6f, 0x55, 0x73, 0x61, 0x67, 0x65, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
248            0x16, 0x00, 0x00, 0x00, 0x54, 0x00, 0x65, 0x00, 0x73, 0x00, 0x74, 0x00, 0x20, 0x00,
249            0x44, 0x00, 0x43, 0x00, 0x2e, 0x00, 0x72, 0x00, 0x64, 0x00, 0x70, 0x00, 0x72, 0x6e,
250            0x65, 0x74, 0x45, 0x67,
251        ];
252        let parsed = decode_content(&data);
253        let notify_response = parsed.content.to_changenotify().unwrap();
254        assert_eq!(
255            notify_response,
256            ChangeNotifyResponse {
257                buffer: vec![
258                    FileNotifyInformation {
259                        action: NotifyAction::Added,
260                        file_name: "11.txt".into()
261                    },
262                    FileNotifyInformation {
263                        action: NotifyAction::Added,
264                        file_name: "kernel.bin.til".into()
265                    },
266                    FileNotifyInformation {
267                        action: NotifyAction::Added,
268                        file_name: "ec2-3-70-222-69.eu-central-1.compute.amazonaws.com.rdp".into()
269                    },
270                    FileNotifyInformation {
271                        action: NotifyAction::Added,
272                        file_name: "ec2-18-198-51-98.eu-central-1.compute.amazonaws.com.rdp".into()
273                    },
274                    FileNotifyInformation {
275                        action: NotifyAction::Added,
276                        file_name: "Test DC.rdp".into()
277                    }
278                ]
279                .into()
280            }
281        );
282    }
283}