]> git.ipfire.org Git - people/ms/suricata.git/blame - rust/src/smb/smb2.rs
rust(lint): use let for binding single value
[people/ms/suricata.git] / rust / src / smb / smb2.rs
CommitLineData
75d7c9d6
VJ
1/* Copyright (C) 2017 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
13b73997
PC
18use nom;
19
42e5065a 20use crate::core::*;
75d7c9d6 21
42e5065a
JI
22use crate::smb::smb::*;
23use crate::smb::smb2_records::*;
24use crate::smb::smb2_session::*;
25use crate::smb::smb2_ioctl::*;
26use crate::smb::dcerpc::*;
27use crate::smb::events::*;
28use crate::smb::files::*;
75d7c9d6
VJ
29
30pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0;
31pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1;
32pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2;
33pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3;
34pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4;
35pub const SMB2_COMMAND_CREATE: u16 = 5;
36pub const SMB2_COMMAND_CLOSE: u16 = 6;
894a73ee 37pub const SMB2_COMMAND_FLUSH: u16 = 7;
75d7c9d6
VJ
38pub const SMB2_COMMAND_READ: u16 = 8;
39pub const SMB2_COMMAND_WRITE: u16 = 9;
894a73ee 40pub const SMB2_COMMAND_LOCK: u16 = 10;
75d7c9d6 41pub const SMB2_COMMAND_IOCTL: u16 = 11;
894a73ee 42pub const SMB2_COMMAND_CANCEL: u16 = 12;
75d7c9d6
VJ
43pub const SMB2_COMMAND_KEEPALIVE: u16 = 13;
44pub const SMB2_COMMAND_FIND: u16 = 14;
894a73ee 45pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15;
75d7c9d6
VJ
46pub const SMB2_COMMAND_GET_INFO: u16 = 16;
47pub const SMB2_COMMAND_SET_INFO: u16 = 17;
894a73ee 48pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18;
75d7c9d6
VJ
49
50pub fn smb2_command_string(c: u16) -> String {
51 match c {
52 SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL",
53 SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP",
54 SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF",
55 SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT",
56 SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT",
57 SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE",
58 SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE",
59 SMB2_COMMAND_READ => "SMB2_COMMAND_READ",
894a73ee 60 SMB2_COMMAND_FLUSH => "SMB2_COMMAND_FLUSH",
75d7c9d6 61 SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE",
894a73ee 62 SMB2_COMMAND_LOCK => "SMB2_COMMAND_LOCK",
75d7c9d6 63 SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL",
894a73ee 64 SMB2_COMMAND_CANCEL => "SMB2_COMMAND_CANCEL",
75d7c9d6
VJ
65 SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE",
66 SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND",
894a73ee 67 SMB2_COMMAND_CHANGE_NOTIFY => "SMB2_COMMAND_CHANGE_NOTIFY",
75d7c9d6
VJ
68 SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO",
69 SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO",
894a73ee 70 SMB2_COMMAND_OPLOCK_BREAK => "SMB2_COMMAND_OPLOCK_BREAK",
75d7c9d6
VJ
71 _ => { return (c).to_string(); },
72 }.to_string()
73
74}
75
76pub fn smb2_dialect_string(d: u16) -> String {
77 match d {
78 0x0202 => "2.02",
79 0x0210 => "2.10",
80 0x0222 => "2.22",
81 0x0224 => "2.24",
82 0x02ff => "2.??",
83 0x0300 => "3.00",
84 0x0302 => "3.02",
85 0x0310 => "3.10",
86 0x0311 => "3.11",
87 _ => { return (d).to_string(); },
88 }.to_string()
89}
90
91// later we'll use this to determine if we need to
92// track a ssn per type
32b19fac
VJ
93fn smb2_create_new_tx(cmd: u16) -> bool {
94 match cmd {
95 SMB2_COMMAND_READ |
96 SMB2_COMMAND_WRITE |
97 SMB2_COMMAND_GET_INFO |
98 SMB2_COMMAND_SET_INFO => { false },
99 _ => { true },
100 }
75d7c9d6
VJ
101}
102
103fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
104{
105 if smb2_create_new_tx(r.command) {
106 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
107 let tx = state.get_generic_tx(2, r.command as u16, &tx_hdr);
108 if let Some(tx) = tx {
109 tx.set_status(r.nt_status, false);
110 tx.response_done = true;
111 }
112 }
113}
114
115pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
116{
117 smb2_read_response_record_generic(state, r);
118
75d7c9d6 119 match parse_smb2_response_read(r.data) {
13b73997 120 Ok((_, rd)) => {
ac4e8885
VJ
121 if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW {
122 SCLogDebug!("SMBv2/READ: incomplete record, expecting a follow up");
123 // fall through
124
125 } else if r.nt_status != SMB_NTSTATUS_SUCCESS {
9dd7c381
VJ
126 SCLogDebug!("SMBv2: read response error code received: skip record");
127 state.set_skip(STREAM_TOCLIENT, rd.len, rd.data.len() as u32);
128 return;
129 }
130
75d7c9d6
VJ
131 SCLogDebug!("SMBv2: read response => {:?}", rd);
132
133 // get the request info. If we don't have it, there is nothing
134 // we can do except skip this record.
135 let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
136 let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) {
137 Some(o) => (o.offset, o.guid),
138 None => {
aa8d64c2
VJ
139 SCLogDebug!("SMBv2 READ response: reply to unknown request {:?}",rd);
140 state.set_skip(STREAM_TOCLIENT, rd.len, rd.data.len() as u32);
75d7c9d6
VJ
141 return;
142 },
143 };
144 SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset);
145
caa79468 146 let mut set_event_fileoverlap = false;
75d7c9d6 147 // look up existing tracker and if we have it update it
fb986abe 148 let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) {
75d7c9d6
VJ
149 Some((tx, files, flags)) => {
150 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
151 let file_id : u32 = tx.id as u32;
caa79468
PA
152 if offset < tdf.file_tracker.tracked {
153 set_event_fileoverlap = true;
154 }
75d7c9d6
VJ
155 filetracker_newchunk(&mut tdf.file_tracker, files, flags,
156 &tdf.file_name, rd.data, offset,
157 rd.len, 0, false, &file_id);
158 }
159 true
160 },
161 None => { false },
162 };
ac4e8885 163 SCLogDebug!("existing file tx? {}", found);
75d7c9d6
VJ
164 if !found {
165 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
ac4e8885 166 let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) {
75d7c9d6
VJ
167 Some(n) => (n.name.to_vec(), n.is_pipe),
168 _ => { (Vec::new(), false) },
169 };
4d044483 170 let mut is_dcerpc = if is_pipe || (share_name.len() == 0 && !is_pipe) {
da0a976e 171 state.get_service_for_guid(&file_guid).1
4d044483
VJ
172 } else {
173 false
75d7c9d6 174 };
4d044483
VJ
175 SCLogDebug!("SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}",
176 share_name, is_pipe, is_dcerpc);
ac4e8885
VJ
177
178 if share_name.len() == 0 && !is_pipe {
179 SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe");
180
4d044483
VJ
181 if smb_dcerpc_probe(rd.data) == true {
182 SCLogDebug!("SMBv2/READ: looks like dcerpc");
183 // insert fake tree to assist in follow up lookups
184 let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true);
185 state.ssn2tree_map.insert(tree_key, tree);
186 if !is_dcerpc {
187 state.guid2name_map.insert(file_guid.to_vec(), b"suricata::dcerpc".to_vec());
188 }
189 is_pipe = true;
190 is_dcerpc = true;
191 } else {
192 SCLogDebug!("SMBv2/READ: not DCERPC");
ac4e8885
VJ
193 }
194 }
195
75d7c9d6
VJ
196 if is_pipe && is_dcerpc {
197 SCLogDebug!("SMBv2 DCERPC read");
198 let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
ac4e8885 199 let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_READ, r.nt_status);
75d7c9d6
VJ
200 smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data);
201 } else if is_pipe {
202 SCLogDebug!("non-DCERPC pipe");
aa8d64c2 203 state.set_skip(STREAM_TOCLIENT, rd.len, rd.data.len() as u32);
75d7c9d6
VJ
204 } else {
205 let file_name = match state.guid2name_map.get(&file_guid) {
206 Some(n) => { n.to_vec() },
15978d4e 207 None => { b"<unknown>".to_vec() },
75d7c9d6
VJ
208 };
209 let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOCLIENT);
210 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
211 let file_id : u32 = tx.id as u32;
caa79468
PA
212 if offset < tdf.file_tracker.tracked {
213 set_event_fileoverlap = true;
214 }
75d7c9d6
VJ
215 filetracker_newchunk(&mut tdf.file_tracker, files, flags,
216 &file_name, rd.data, offset,
217 rd.len, 0, false, &file_id);
218 tdf.share_name = share_name;
219 }
220 tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ);
221 tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
222 r.session_id, r.tree_id, 0); // TODO move into new_file_tx
223 }
224 }
225
caa79468
PA
226 if set_event_fileoverlap {
227 state.set_event(SMBEvent::FileOverlap);
228 }
4d58aaae 229 state.set_file_left(STREAM_TOCLIENT, rd.len, rd.data.len() as u32, file_guid.to_vec());
75d7c9d6
VJ
230 }
231 _ => {
ac4e8885 232 SCLogDebug!("SMBv2: failed to parse read response");
75d7c9d6
VJ
233 state.set_event(SMBEvent::MalformedData);
234 }
235 }
236}
237
238pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
239{
ac4e8885 240 SCLogDebug!("SMBv2/WRITE: request record");
75d7c9d6
VJ
241 if smb2_create_new_tx(r.command) {
242 let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
243 let tx = state.new_generic_tx(2, r.command, tx_key);
244 tx.request_done = true;
245 }
246 match parse_smb2_request_write(r.data) {
13b73997 247 Ok((_, wr)) => {
75d7c9d6
VJ
248 /* update key-guid map */
249 let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID);
250 state.ssn2vec_map.insert(guid_key, wr.guid.to_vec());
251
252 let file_guid = wr.guid.to_vec();
253 let file_name = match state.guid2name_map.get(&file_guid) {
254 Some(n) => n.to_vec(),
255 None => Vec::new(),
256 };
257
caa79468 258 let mut set_event_fileoverlap = false;
fb986abe 259 let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOSERVER) {
75d7c9d6
VJ
260 Some((tx, files, flags)) => {
261 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
262 let file_id : u32 = tx.id as u32;
caa79468
PA
263 if wr.wr_offset < tdf.file_tracker.tracked {
264 set_event_fileoverlap = true;
265 }
75d7c9d6
VJ
266 filetracker_newchunk(&mut tdf.file_tracker, files, flags,
267 &file_name, wr.data, wr.wr_offset,
268 wr.wr_len, 0, false, &file_id);
269 }
270 true
271 },
272 None => { false },
273 };
274 if !found {
275 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
ac4e8885
VJ
276 let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) {
277 Some(n) => { (n.name.to_vec(), n.is_pipe) },
278 _ => { (Vec::new(), false) },
75d7c9d6 279 };
ac4e8885 280 let mut is_dcerpc = if is_pipe || (share_name.len() == 0 && !is_pipe) {
da0a976e 281 state.get_service_for_guid(wr.guid).1
ac4e8885
VJ
282 } else {
283 false
75d7c9d6 284 };
4d044483
VJ
285 SCLogDebug!("SMBv2/WRITE: share_name {:?} is_pipe {} is_dcerpc {}",
286 share_name, is_pipe, is_dcerpc);
ac4e8885
VJ
287
288 // if we missed the TREE connect we can't be sure if 'is_dcerpc' is correct
289 if share_name.len() == 0 && !is_pipe {
290 SCLogDebug!("SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe");
291
4d044483
VJ
292 if smb_dcerpc_probe(wr.data) == true {
293 SCLogDebug!("SMBv2/WRITE: looks like we have dcerpc");
ac4e8885 294
4d044483
VJ
295 let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true);
296 state.ssn2tree_map.insert(tree_key, tree);
297 if !is_dcerpc {
298 state.guid2name_map.insert(file_guid.to_vec(),
299 b"suricata::dcerpc".to_vec());
300 }
301 is_pipe = true;
302 is_dcerpc = true;
303 } else {
304 SCLogDebug!("SMBv2/WRITE: not DCERPC");
ac4e8885
VJ
305 }
306 }
75d7c9d6
VJ
307 if is_pipe && is_dcerpc {
308 SCLogDebug!("SMBv2 DCERPC write");
309 let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
310 let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE);
311 smb_write_dcerpc_record(state, vercmd, hdr, wr.data);
312 } else if is_pipe {
313 SCLogDebug!("non-DCERPC pipe: skip rest of the record");
aa8d64c2 314 state.set_skip(STREAM_TOSERVER, wr.wr_len, wr.data.len() as u32);
75d7c9d6
VJ
315 } else {
316 let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOSERVER);
317 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
318 let file_id : u32 = tx.id as u32;
caa79468
PA
319 if wr.wr_offset < tdf.file_tracker.tracked {
320 set_event_fileoverlap = true;
321 }
75d7c9d6
VJ
322 filetracker_newchunk(&mut tdf.file_tracker, files, flags,
323 &file_name, wr.data, wr.wr_offset,
324 wr.wr_len, 0, false, &file_id);
325 }
326 tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE);
327 tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER,
328 r.session_id, r.tree_id, 0); // TODO move into new_file_tx
329 }
330 }
caa79468
PA
331
332 if set_event_fileoverlap {
333 state.set_event(SMBEvent::FileOverlap);
334 }
4d58aaae 335 state.set_file_left(STREAM_TOSERVER, wr.wr_len, wr.data.len() as u32, file_guid.to_vec());
75d7c9d6
VJ
336 },
337 _ => {
338 state.set_event(SMBEvent::MalformedData);
339 },
340 }
341}
342
343pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
344{
345 SCLogDebug!("SMBv2 request record, command {} tree {} session {}",
346 &smb2_command_string(r.command), r.tree_id, r.session_id);
347
75d7c9d6
VJ
348 let mut events : Vec<SMBEvent> = Vec::new();
349
350 let have_tx = match r.command {
7b61f2c5
VJ
351 SMB2_COMMAND_SET_INFO => {
352 SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", r);
353 let have_si_tx = match parse_smb2_request_setinfo(r.data) {
13b73997 354 Ok((_, rd)) => {
7b61f2c5
VJ
355 SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", rd);
356
fde753d9
PA
357 match rd.data {
358 Smb2SetInfoRequestData::RENAME(ref ren) => {
359 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
360 let mut newname = ren.name.to_vec();
361 newname.retain(|&i|i != 0x00);
362 let oldname = match state.guid2name_map.get(rd.guid) {
363 Some(n) => { n.to_vec() },
364 None => { b"<unknown>".to_vec() },
365 };
366 let tx = state.new_rename_tx(rd.guid.to_vec(), oldname, newname);
367 tx.hdr = tx_hdr;
368 tx.request_done = true;
369 tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO);
370 true
371 }
372 Smb2SetInfoRequestData::DISPOSITION(ref dis) => {
373 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
374 let fname = match state.guid2name_map.get(rd.guid) {
375 Some(n) => { n.to_vec() },
9b8be5a6
PA
376 None => {
377 // try to find latest created file in case of chained commands
378 let mut guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME);
379 guid_key.msg_id = guid_key.msg_id - 1;
380 match state.ssn2vec_map.get(&guid_key) {
381 Some(n) => { n.to_vec() },
382 None => { b"<unknown>".to_vec()},
383 }
384 },
fde753d9
PA
385 };
386 let tx = state.new_setfileinfo_tx(fname, rd.guid.to_vec(), rd.class as u16, rd.infolvl as u16, dis.delete);
387 tx.hdr = tx_hdr;
388 tx.request_done = true;
389 tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO);
390 true
391 }
392 _ => false,
7b61f2c5
VJ
393 }
394 },
13b73997 395 Err(nom::Err::Incomplete(_n)) => {
7b61f2c5
VJ
396 SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _n);
397 events.push(SMBEvent::MalformedData);
398 false
399 },
13b73997
PC
400 Err(nom::Err::Error(_e)) |
401 Err(nom::Err::Failure(_e)) => {
7b61f2c5
VJ
402 SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _e);
403 events.push(SMBEvent::MalformedData);
404 false
405 },
406 };
407 have_si_tx
408 },
75d7c9d6 409 SMB2_COMMAND_IOCTL => {
283be3ca
VJ
410 smb2_ioctl_request_record(state, r);
411 true
75d7c9d6
VJ
412 },
413 SMB2_COMMAND_TREE_DISCONNECT => {
414 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
415 state.ssn2tree_map.remove(&tree_key);
416 false
417 }
418 SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
419 match parse_smb2_request_negotiate_protocol(r.data) {
13b73997 420 Ok((_, rd)) => {
75d7c9d6
VJ
421 let mut dialects : Vec<Vec<u8>> = Vec::new();
422 for d in rd.dialects_vec {
423 SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d));
424 let dvec = smb2_dialect_string(d).as_bytes().to_vec();
425 dialects.push(dvec);
426 }
427
428 let found = match state.get_negotiate_tx(2) {
429 Some(_) => {
ea1e13cb 430 SCLogDebug!("WEIRD, should not have NEGOTIATE tx!");
75d7c9d6
VJ
431 true
432 },
433 None => { false },
434 };
435 if !found {
436 let tx = state.new_negotiate_tx(2);
437 if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
438 tdn.dialects2 = dialects;
6d56edc3 439 tdn.client_guid = Some(rd.client_guid.to_vec());
75d7c9d6
VJ
440 }
441 tx.request_done = true;
442 }
443 true
444 },
445 _ => {
446 events.push(SMBEvent::MalformedData);
447 false
448 },
449 }
450 },
451 SMB2_COMMAND_SESSION_SETUP => {
8bef1208
VJ
452 smb2_session_setup_request(state, r);
453 true
75d7c9d6
VJ
454 },
455 SMB2_COMMAND_TREE_CONNECT => {
456 match parse_smb2_request_tree_connect(r.data) {
13b73997 457 Ok((_, tr)) => {
75d7c9d6
VJ
458 let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
459 let mut name_val = tr.share_name.to_vec();
460 name_val.retain(|&i|i != 0x00);
461 if name_val.len() > 1 {
462 name_val = name_val[1..].to_vec();
463 }
464
465 let tx = state.new_treeconnect_tx(name_key, name_val);
466 tx.request_done = true;
467 tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT);
468 true
469 }
470 _ => {
471 events.push(SMBEvent::MalformedData);
472 false
473 },
474 }
475 },
476 SMB2_COMMAND_READ => {
477 match parse_smb2_request_read(r.data) {
13b73997 478 Ok((_, rd)) => {
75d7c9d6
VJ
479 SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}",
480 rd.guid, rd.rd_len, rd.rd_offset);
481
482 // store read guid,offset in map
483 let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
484 let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset);
485 state.ssn2vecoffset_map.insert(guid_key, guidoff);
486 },
487 _ => {
488 events.push(SMBEvent::MalformedData);
489 },
490 }
491 false
492 },
493 SMB2_COMMAND_CREATE => {
494 match parse_smb2_request_create(r.data) {
13b73997 495 Ok((_, cr)) => {
75d7c9d6
VJ
496 let del = cr.create_options & 0x0000_1000 != 0;
497 let dir = cr.create_options & 0x0000_0001 != 0;
498
499 SCLogDebug!("create_options {:08x}", cr.create_options);
500
501 let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME);
502 state.ssn2vec_map.insert(name_key, cr.data.to_vec());
503
504 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
505 let tx = state.new_create_tx(&cr.data.to_vec(),
506 cr.disposition, del, dir, tx_hdr);
507 tx.vercmd.set_smb2_cmd(r.command);
508 SCLogDebug!("TS CREATE TX {} created", tx.id);
509 true
510 },
511 _ => {
512 events.push(SMBEvent::MalformedData);
513 false
514 },
515 }
516 },
517 SMB2_COMMAND_WRITE => {
69cf5c9e 518 smb2_write_request_record(state, r);
75d7c9d6
VJ
519 true // write handling creates both file tx and generic tx
520 },
521 SMB2_COMMAND_CLOSE => {
522 match parse_smb2_request_close(r.data) {
13b73997 523 Ok((_, cd)) => {
fb986abe 524 let found_ts = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), STREAM_TOSERVER) {
75d7c9d6
VJ
525 Some((tx, files, flags)) => {
526 if !tx.request_done {
527 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
528 tdf.file_tracker.close(files, flags);
529 }
530 }
531 tx.request_done = true;
532 tx.response_done = true;
533 tx.set_status(SMB_NTSTATUS_SUCCESS, false);
534 true
535 },
536 None => { false },
537 };
fb986abe 538 let found_tc = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), STREAM_TOCLIENT) {
75d7c9d6
VJ
539 Some((tx, files, flags)) => {
540 if !tx.request_done {
541 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
542 tdf.file_tracker.close(files, flags);
543 }
544 }
545 tx.request_done = true;
546 tx.response_done = true;
547 tx.set_status(SMB_NTSTATUS_SUCCESS, false);
548 true
549 },
550 None => { false },
551 };
552 if !found_ts && !found_tc {
553 SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid);
554 }
555 },
556 _ => {
557 events.push(SMBEvent::MalformedData);
558 },
559 }
560 false
561 },
562 _ => {
563 false
564 },
565 };
566 /* if we don't have a tx, create it here (maybe) */
567 if !have_tx {
568 if smb2_create_new_tx(r.command) {
283be3ca 569 let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
75d7c9d6
VJ
570 let tx = state.new_generic_tx(2, r.command, tx_key);
571 SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}",
283be3ca 572 tx.id, r.command, r.session_id, r.tree_id, r.message_id);
75d7c9d6
VJ
573 tx.set_events(events);
574 }
575 }
576}
577
75d7c9d6
VJ
578pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>)
579{
580 SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}",
581 &smb2_command_string(r.command), &smb_ntstatus_string(r.nt_status),
582 r.tree_id, r.session_id, r.message_id);
583
75d7c9d6
VJ
584 let mut events : Vec<SMBEvent> = Vec::new();
585
586 let have_tx = match r.command {
587 SMB2_COMMAND_IOCTL => {
283be3ca
VJ
588 smb2_ioctl_response_record(state, r);
589 true
75d7c9d6
VJ
590 },
591 SMB2_COMMAND_SESSION_SETUP => {
8bef1208 592 smb2_session_setup_response(state, r);
75d7c9d6
VJ
593 true
594 },
595 SMB2_COMMAND_WRITE => {
ecbf10da
VJ
596 if r.nt_status == SMB_NTSTATUS_SUCCESS {
597 match parse_smb2_response_write(r.data)
598 {
ef575533
PA
599 Ok((_, _wr)) => {
600 SCLogDebug!("SMBv2: Write response => {:?}", _wr);
ecbf10da
VJ
601
602 /* search key-guid map */
603 let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID,
604 r.session_id, r.tree_id, r.message_id);
ef575533 605 let _guid_vec = match state.ssn2vec_map.remove(&guid_key) {
ecbf10da
VJ
606 Some(p) => p,
607 None => {
608 SCLogDebug!("SMBv2 response: GUID NOT FOUND");
609 Vec::new()
610 },
611 };
ef575533 612 SCLogDebug!("SMBv2 write response for GUID {:?}", _guid_vec);
ecbf10da
VJ
613 }
614 _ => {
615 events.push(SMBEvent::MalformedData);
616 },
75d7c9d6 617 }
75d7c9d6
VJ
618 }
619 false // the request may have created a generic tx, so handle that here
620 },
621 SMB2_COMMAND_READ => {
ac4e8885
VJ
622 if r.nt_status == SMB_NTSTATUS_SUCCESS ||
623 r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW {
69cf5c9e 624 smb2_read_response_record(state, r);
75d7c9d6
VJ
625 false
626
627 } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE {
628 SCLogDebug!("SMBv2: read response => EOF");
629
630 let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET);
631 let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) {
632 Some(o) => o.guid,
633 _ => {
ea1e13cb 634 SCLogDebug!("SMBv2 READ response: reply to unknown request");
75d7c9d6
VJ
635 Vec::new()
636 },
637 };
fb986abe 638 let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) {
75d7c9d6
VJ
639 Some((tx, files, flags)) => {
640 if !tx.request_done {
641 if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
642 tdf.file_tracker.close(files, flags);
643 }
644 }
645 tx.set_status(r.nt_status, false);
646 tx.request_done = true;
647 false
648 },
649 None => { false },
650 };
651 if !found {
652 SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid);
653 }
654 false
655 } else {
ea1e13cb 656 SCLogDebug!("SMBv2 READ: status {}", &smb_ntstatus_string(r.nt_status));
75d7c9d6
VJ
657 false
658 }
659 },
660 SMB2_COMMAND_CREATE => {
661 if r.nt_status == SMB_NTSTATUS_SUCCESS {
662 match parse_smb2_response_create(r.data) {
13b73997 663 Ok((_, cr)) => {
75d7c9d6
VJ
664 SCLogDebug!("SMBv2: Create response => {:?}", cr);
665
666 let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME);
0e05ef73
VJ
667 if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) {
668 p.retain(|&i|i != 0x00);
669 state.guid2name_map.insert(cr.guid.to_vec(), p);
670 } else {
671 SCLogDebug!("SMBv2 response: GUID NOT FOUND");
672 }
673
674 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
675 if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) {
676 SCLogDebug!("tx {} with {}/{} marked as done",
677 tx.id, r.command, &smb2_command_string(r.command));
678 tx.set_status(r.nt_status, false);
679 tx.response_done = true;
680
681 if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data {
682 tdn.create_ts = cr.create_ts.as_unix();
683 tdn.last_access_ts = cr.last_access_ts.as_unix();
684 tdn.last_write_ts = cr.last_write_ts.as_unix();
685 tdn.last_change_ts = cr.last_change_ts.as_unix();
686 tdn.size = cr.size;
fb986abe 687 tdn.guid = cr.guid.to_vec();
0e05ef73 688 }
75d7c9d6
VJ
689 }
690 }
691 _ => {
692 events.push(SMBEvent::MalformedData);
693 },
694 }
0e05ef73
VJ
695 true
696 } else {
697 false
75d7c9d6 698 }
75d7c9d6
VJ
699 },
700 SMB2_COMMAND_TREE_DISCONNECT => {
701 // normally removed when processing request,
702 // but in case we missed that try again here
703 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
704 state.ssn2tree_map.remove(&tree_key);
705 false
706 }
707 SMB2_COMMAND_TREE_CONNECT => {
708 if r.nt_status == SMB_NTSTATUS_SUCCESS {
709 match parse_smb2_response_tree_connect(r.data) {
13b73997 710 Ok((_, tr)) => {
75d7c9d6
VJ
711 let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
712 let mut share_name = Vec::new();
713 let is_pipe = tr.share_type == 2;
714 let found = match state.get_treeconnect_tx(name_key) {
715 Some(tx) => {
716 if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data {
c56f5e11 717 tdn.share_type = tr.share_type;
75d7c9d6
VJ
718 tdn.is_pipe = is_pipe;
719 tdn.tree_id = r.tree_id as u32;
720 share_name = tdn.share_name.to_vec();
721 }
722 // update hdr now that we have a tree_id
723 tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER);
724 tx.response_done = true;
725 tx.set_status(r.nt_status, false);
726 true
727 },
728 None => { false },
729 };
730 if found {
731 let tree = SMBTree::new(share_name.to_vec(), is_pipe);
732 let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE);
733 state.ssn2tree_map.insert(tree_key, tree);
734 }
735 true
736 }
737 _ => {
738 events.push(SMBEvent::MalformedData);
739 false
740 },
741 }
742 } else {
743 let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE);
744 let found = match state.get_treeconnect_tx(name_key) {
745 Some(tx) => {
746 tx.response_done = true;
747 tx.set_status(r.nt_status, false);
748 true
749 },
750 None => { false },
751 };
752 found
753 }
754 },
755 SMB2_COMMAND_NEGOTIATE_PROTOCOL => {
6d56edc3
VJ
756 let res = if r.nt_status == SMB_NTSTATUS_SUCCESS {
757 parse_smb2_response_negotiate_protocol(r.data)
758 } else {
759 parse_smb2_response_negotiate_protocol_error(r.data)
760 };
761 match res {
13b73997 762 Ok((_, rd)) => {
75d7c9d6
VJ
763 SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect));
764
765 state.dialect = rd.dialect;
766 let found2 = match state.get_negotiate_tx(2) {
767 Some(tx) => {
6d56edc3
VJ
768 if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
769 tdn.server_guid = rd.server_guid.to_vec();
770 }
75d7c9d6
VJ
771 tx.set_status(r.nt_status, false);
772 tx.response_done = true;
773 true
774 },
775 None => { false },
776 };
777 // SMB2 response to SMB1 request?
778 let found1 = !found2 && match state.get_negotiate_tx(1) {
779 Some(tx) => {
6d56edc3
VJ
780 if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data {
781 tdn.server_guid = rd.server_guid.to_vec();
782 }
75d7c9d6
VJ
783 tx.set_status(r.nt_status, false);
784 tx.response_done = true;
785 true
786 },
ecbf10da 787 None => { false },
75d7c9d6
VJ
788 };
789 found1 || found2
790 },
791 _ => {
792 events.push(SMBEvent::MalformedData);
793 false
794 }
795 }
796 },
797 _ => {
798 SCLogDebug!("default case: no TX");
799 false
800 },
801 };
802 if !have_tx {
283be3ca 803 let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX);
75d7c9d6
VJ
804 SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}",
805 &smb2_command_string(r.command),
283be3ca 806 r.session_id, r.tree_id, r.message_id);
75d7c9d6
VJ
807 let _found = match state.get_generic_tx(2, r.command, &tx_hdr) {
808 Some(tx) => {
809 SCLogDebug!("tx {} with {}/{} marked as done",
810 tx.id, r.command, &smb2_command_string(r.command));
811 if r.nt_status != SMB_NTSTATUS_PENDING {
812 tx.response_done = true;
813 }
814 tx.set_status(r.nt_status, false);
815 tx.set_events(events);
816 true
817 },
818 _ => {
ea1e13cb 819 SCLogDebug!("no tx found for {:?}", r);
75d7c9d6
VJ
820 false
821 },
822 };
823 }
824}