]>
Commit | Line | Data |
---|---|---|
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 | ||
18 | use core::*; | |
19 | use log::*; | |
20 | use nom::IResult; | |
21 | ||
22 | use smb::smb::*; | |
23 | use smb::smb2_records::*; | |
8bef1208 | 24 | use smb::smb2_session::*; |
283be3ca | 25 | use smb::smb2_ioctl::*; |
75d7c9d6 VJ |
26 | use smb::dcerpc::*; |
27 | use smb::events::*; | |
75d7c9d6 VJ |
28 | use smb::files::*; |
29 | ||
30 | pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; | |
31 | pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1; | |
32 | pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2; | |
33 | pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3; | |
34 | pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4; | |
35 | pub const SMB2_COMMAND_CREATE: u16 = 5; | |
36 | pub const SMB2_COMMAND_CLOSE: u16 = 6; | |
894a73ee | 37 | pub const SMB2_COMMAND_FLUSH: u16 = 7; |
75d7c9d6 VJ |
38 | pub const SMB2_COMMAND_READ: u16 = 8; |
39 | pub const SMB2_COMMAND_WRITE: u16 = 9; | |
894a73ee | 40 | pub const SMB2_COMMAND_LOCK: u16 = 10; |
75d7c9d6 | 41 | pub const SMB2_COMMAND_IOCTL: u16 = 11; |
894a73ee | 42 | pub const SMB2_COMMAND_CANCEL: u16 = 12; |
75d7c9d6 VJ |
43 | pub const SMB2_COMMAND_KEEPALIVE: u16 = 13; |
44 | pub const SMB2_COMMAND_FIND: u16 = 14; | |
894a73ee | 45 | pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15; |
75d7c9d6 VJ |
46 | pub const SMB2_COMMAND_GET_INFO: u16 = 16; |
47 | pub const SMB2_COMMAND_SET_INFO: u16 = 17; | |
894a73ee | 48 | pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18; |
75d7c9d6 VJ |
49 | |
50 | pub 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 | ||
76 | pub 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 | |
93 | fn smb2_create_new_tx(_cmd: u16) -> bool { | |
94 | true | |
95 | } | |
96 | ||
97 | fn smb2_read_response_record_generic<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
98 | { | |
99 | if smb2_create_new_tx(r.command) { | |
100 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
101 | let tx = state.get_generic_tx(2, r.command as u16, &tx_hdr); | |
102 | if let Some(tx) = tx { | |
103 | tx.set_status(r.nt_status, false); | |
104 | tx.response_done = true; | |
105 | } | |
106 | } | |
107 | } | |
108 | ||
109 | pub fn smb2_read_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
110 | { | |
111 | smb2_read_response_record_generic(state, r); | |
112 | ||
113 | if r.nt_status != SMB_NTSTATUS_SUCCESS { | |
114 | return; | |
115 | } | |
116 | ||
117 | match parse_smb2_response_read(r.data) { | |
118 | IResult::Done(_, rd) => { | |
119 | SCLogDebug!("SMBv2: read response => {:?}", rd); | |
120 | ||
121 | // get the request info. If we don't have it, there is nothing | |
122 | // we can do except skip this record. | |
123 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
124 | let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) { | |
125 | Some(o) => (o.offset, o.guid), | |
126 | None => { | |
127 | SCLogDebug!("SMBv2 READ response: reply to unknown request"); | |
128 | state.skip_tc = rd.len - rd.data.len() as u32; | |
129 | return; | |
130 | }, | |
131 | }; | |
132 | SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset); | |
133 | ||
134 | // look up existing tracker and if we have it update it | |
fb986abe | 135 | let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) { |
75d7c9d6 VJ |
136 | Some((tx, files, flags)) => { |
137 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
138 | let file_id : u32 = tx.id as u32; | |
139 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
140 | &tdf.file_name, rd.data, offset, | |
141 | rd.len, 0, false, &file_id); | |
142 | } | |
143 | true | |
144 | }, | |
145 | None => { false }, | |
146 | }; | |
147 | if !found { | |
148 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
149 | let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) { | |
150 | Some(n) => (n.name.to_vec(), n.is_pipe), | |
151 | _ => { (Vec::new(), false) }, | |
152 | }; | |
153 | let is_dcerpc = is_pipe && match state.get_service_for_guid(&file_guid) { | |
154 | (_, x) => x, | |
155 | }; | |
156 | if is_pipe && is_dcerpc { | |
157 | SCLogDebug!("SMBv2 DCERPC read"); | |
158 | let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
159 | let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_READ); | |
160 | smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data); | |
161 | } else if is_pipe { | |
162 | SCLogDebug!("non-DCERPC pipe"); | |
163 | state.skip_tc = rd.len - rd.data.len() as u32; | |
164 | } else { | |
165 | let file_name = match state.guid2name_map.get(&file_guid) { | |
166 | Some(n) => { n.to_vec() }, | |
167 | None => { Vec::new() }, | |
168 | }; | |
169 | let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOCLIENT); | |
170 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
171 | let file_id : u32 = tx.id as u32; | |
172 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
173 | &file_name, rd.data, offset, | |
174 | rd.len, 0, false, &file_id); | |
175 | tdf.share_name = share_name; | |
176 | } | |
177 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ); | |
178 | tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, | |
179 | r.session_id, r.tree_id, 0); // TODO move into new_file_tx | |
180 | } | |
181 | } | |
182 | ||
183 | state.file_tc_left = rd.len - rd.data.len() as u32; | |
184 | state.file_tc_guid = file_guid.to_vec(); | |
185 | SCLogDebug!("SMBv2 READ RESPONSE: {} bytes left", state.file_tc_left); | |
186 | } | |
187 | _ => { | |
188 | state.set_event(SMBEvent::MalformedData); | |
189 | } | |
190 | } | |
191 | } | |
192 | ||
193 | pub fn smb2_write_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
194 | { | |
195 | if smb2_create_new_tx(r.command) { | |
196 | let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
197 | let tx = state.new_generic_tx(2, r.command, tx_key); | |
198 | tx.request_done = true; | |
199 | } | |
200 | match parse_smb2_request_write(r.data) { | |
201 | IResult::Done(_, wr) => { | |
202 | /* update key-guid map */ | |
203 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID); | |
204 | state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); | |
205 | ||
206 | let file_guid = wr.guid.to_vec(); | |
207 | let file_name = match state.guid2name_map.get(&file_guid) { | |
208 | Some(n) => n.to_vec(), | |
209 | None => Vec::new(), | |
210 | }; | |
211 | ||
fb986abe | 212 | let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOSERVER) { |
75d7c9d6 VJ |
213 | Some((tx, files, flags)) => { |
214 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
215 | let file_id : u32 = tx.id as u32; | |
216 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
217 | &file_name, wr.data, wr.wr_offset, | |
218 | wr.wr_len, 0, false, &file_id); | |
219 | } | |
220 | true | |
221 | }, | |
222 | None => { false }, | |
223 | }; | |
224 | if !found { | |
225 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
226 | let is_pipe = match state.ssn2tree_map.get(&tree_key) { | |
227 | Some(n) => { n.is_pipe }, | |
228 | _ => { false }, | |
229 | }; | |
230 | let is_dcerpc = is_pipe && match state.get_service_for_guid(&wr.guid) { | |
231 | (_, x) => x, | |
232 | }; | |
233 | if is_pipe && is_dcerpc { | |
234 | SCLogDebug!("SMBv2 DCERPC write"); | |
235 | let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
236 | let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE); | |
237 | smb_write_dcerpc_record(state, vercmd, hdr, wr.data); | |
238 | } else if is_pipe { | |
239 | SCLogDebug!("non-DCERPC pipe: skip rest of the record"); | |
240 | state.skip_ts = wr.wr_len - wr.data.len() as u32; | |
241 | } else { | |
242 | let (tx, files, flags) = state.new_file_tx(&file_guid, &file_name, STREAM_TOSERVER); | |
243 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
244 | let file_id : u32 = tx.id as u32; | |
245 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
246 | &file_name, wr.data, wr.wr_offset, | |
247 | wr.wr_len, 0, false, &file_id); | |
248 | } | |
249 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE); | |
250 | tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, | |
251 | r.session_id, r.tree_id, 0); // TODO move into new_file_tx | |
252 | } | |
253 | } | |
254 | state.file_ts_left = wr.wr_len - wr.data.len() as u32; | |
255 | state.file_ts_guid = file_guid.to_vec(); | |
256 | SCLogDebug!("SMBv2 WRITE REQUEST: {} bytes left", state.file_ts_left); | |
257 | ||
258 | }, | |
259 | _ => { | |
260 | state.set_event(SMBEvent::MalformedData); | |
261 | }, | |
262 | } | |
263 | } | |
264 | ||
265 | pub fn smb2_request_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) | |
266 | { | |
267 | SCLogDebug!("SMBv2 request record, command {} tree {} session {}", | |
268 | &smb2_command_string(r.command), r.tree_id, r.session_id); | |
269 | ||
75d7c9d6 VJ |
270 | let mut events : Vec<SMBEvent> = Vec::new(); |
271 | ||
272 | let have_tx = match r.command { | |
273 | SMB2_COMMAND_IOCTL => { | |
283be3ca VJ |
274 | smb2_ioctl_request_record(state, r); |
275 | true | |
75d7c9d6 VJ |
276 | }, |
277 | SMB2_COMMAND_TREE_DISCONNECT => { | |
278 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
279 | state.ssn2tree_map.remove(&tree_key); | |
280 | false | |
281 | } | |
282 | SMB2_COMMAND_NEGOTIATE_PROTOCOL => { | |
283 | match parse_smb2_request_negotiate_protocol(r.data) { | |
284 | IResult::Done(_, rd) => { | |
285 | let mut dialects : Vec<Vec<u8>> = Vec::new(); | |
286 | for d in rd.dialects_vec { | |
287 | SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d)); | |
288 | let dvec = smb2_dialect_string(d).as_bytes().to_vec(); | |
289 | dialects.push(dvec); | |
290 | } | |
291 | ||
292 | let found = match state.get_negotiate_tx(2) { | |
293 | Some(_) => { | |
294 | SCLogNotice!("WEIRD, should not have NEGOTIATE tx!"); | |
295 | true | |
296 | }, | |
297 | None => { false }, | |
298 | }; | |
299 | if !found { | |
300 | let tx = state.new_negotiate_tx(2); | |
301 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { | |
302 | tdn.dialects2 = dialects; | |
6d56edc3 | 303 | tdn.client_guid = Some(rd.client_guid.to_vec()); |
75d7c9d6 VJ |
304 | } |
305 | tx.request_done = true; | |
306 | } | |
307 | true | |
308 | }, | |
309 | _ => { | |
310 | events.push(SMBEvent::MalformedData); | |
311 | false | |
312 | }, | |
313 | } | |
314 | }, | |
315 | SMB2_COMMAND_SESSION_SETUP => { | |
8bef1208 VJ |
316 | smb2_session_setup_request(state, r); |
317 | true | |
75d7c9d6 VJ |
318 | }, |
319 | SMB2_COMMAND_TREE_CONNECT => { | |
320 | match parse_smb2_request_tree_connect(r.data) { | |
321 | IResult::Done(_, tr) => { | |
322 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); | |
323 | let mut name_val = tr.share_name.to_vec(); | |
324 | name_val.retain(|&i|i != 0x00); | |
325 | if name_val.len() > 1 { | |
326 | name_val = name_val[1..].to_vec(); | |
327 | } | |
328 | ||
329 | let tx = state.new_treeconnect_tx(name_key, name_val); | |
330 | tx.request_done = true; | |
331 | tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT); | |
332 | true | |
333 | } | |
334 | _ => { | |
335 | events.push(SMBEvent::MalformedData); | |
336 | false | |
337 | }, | |
338 | } | |
339 | }, | |
340 | SMB2_COMMAND_READ => { | |
341 | match parse_smb2_request_read(r.data) { | |
342 | IResult::Done(_, rd) => { | |
343 | SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}", | |
344 | rd.guid, rd.rd_len, rd.rd_offset); | |
345 | ||
346 | // store read guid,offset in map | |
347 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
348 | let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset); | |
349 | state.ssn2vecoffset_map.insert(guid_key, guidoff); | |
350 | }, | |
351 | _ => { | |
352 | events.push(SMBEvent::MalformedData); | |
353 | }, | |
354 | } | |
355 | false | |
356 | }, | |
357 | SMB2_COMMAND_CREATE => { | |
358 | match parse_smb2_request_create(r.data) { | |
359 | IResult::Done(_, cr) => { | |
360 | let del = cr.create_options & 0x0000_1000 != 0; | |
361 | let dir = cr.create_options & 0x0000_0001 != 0; | |
362 | ||
363 | SCLogDebug!("create_options {:08x}", cr.create_options); | |
364 | ||
365 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); | |
366 | state.ssn2vec_map.insert(name_key, cr.data.to_vec()); | |
367 | ||
368 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
369 | let tx = state.new_create_tx(&cr.data.to_vec(), | |
370 | cr.disposition, del, dir, tx_hdr); | |
371 | tx.vercmd.set_smb2_cmd(r.command); | |
372 | SCLogDebug!("TS CREATE TX {} created", tx.id); | |
373 | true | |
374 | }, | |
375 | _ => { | |
376 | events.push(SMBEvent::MalformedData); | |
377 | false | |
378 | }, | |
379 | } | |
380 | }, | |
381 | SMB2_COMMAND_WRITE => { | |
382 | smb2_write_request_record(state, &r); | |
383 | true // write handling creates both file tx and generic tx | |
384 | }, | |
385 | SMB2_COMMAND_CLOSE => { | |
386 | match parse_smb2_request_close(r.data) { | |
387 | IResult::Done(_, cd) => { | |
fb986abe | 388 | let found_ts = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), STREAM_TOSERVER) { |
75d7c9d6 VJ |
389 | Some((tx, files, flags)) => { |
390 | if !tx.request_done { | |
391 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
392 | tdf.file_tracker.close(files, flags); | |
393 | } | |
394 | } | |
395 | tx.request_done = true; | |
396 | tx.response_done = true; | |
397 | tx.set_status(SMB_NTSTATUS_SUCCESS, false); | |
398 | true | |
399 | }, | |
400 | None => { false }, | |
401 | }; | |
fb986abe | 402 | let found_tc = match state.get_file_tx_by_fuid(&cd.guid.to_vec(), STREAM_TOCLIENT) { |
75d7c9d6 VJ |
403 | Some((tx, files, flags)) => { |
404 | if !tx.request_done { | |
405 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
406 | tdf.file_tracker.close(files, flags); | |
407 | } | |
408 | } | |
409 | tx.request_done = true; | |
410 | tx.response_done = true; | |
411 | tx.set_status(SMB_NTSTATUS_SUCCESS, false); | |
412 | true | |
413 | }, | |
414 | None => { false }, | |
415 | }; | |
416 | if !found_ts && !found_tc { | |
417 | SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid); | |
418 | } | |
419 | }, | |
420 | _ => { | |
421 | events.push(SMBEvent::MalformedData); | |
422 | }, | |
423 | } | |
424 | false | |
425 | }, | |
426 | _ => { | |
427 | false | |
428 | }, | |
429 | }; | |
430 | /* if we don't have a tx, create it here (maybe) */ | |
431 | if !have_tx { | |
432 | if smb2_create_new_tx(r.command) { | |
283be3ca | 433 | let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); |
75d7c9d6 VJ |
434 | let tx = state.new_generic_tx(2, r.command, tx_key); |
435 | SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}", | |
283be3ca | 436 | tx.id, r.command, r.session_id, r.tree_id, r.message_id); |
75d7c9d6 VJ |
437 | tx.set_events(events); |
438 | } | |
439 | } | |
440 | } | |
441 | ||
75d7c9d6 VJ |
442 | pub fn smb2_response_record<'b>(state: &mut SMBState, r: &Smb2Record<'b>) |
443 | { | |
444 | SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", | |
445 | &smb2_command_string(r.command), &smb_ntstatus_string(r.nt_status), | |
446 | r.tree_id, r.session_id, r.message_id); | |
447 | ||
75d7c9d6 VJ |
448 | let mut events : Vec<SMBEvent> = Vec::new(); |
449 | ||
450 | let have_tx = match r.command { | |
451 | SMB2_COMMAND_IOCTL => { | |
283be3ca VJ |
452 | smb2_ioctl_response_record(state, r); |
453 | true | |
75d7c9d6 VJ |
454 | }, |
455 | SMB2_COMMAND_SESSION_SETUP => { | |
8bef1208 | 456 | smb2_session_setup_response(state, r); |
75d7c9d6 VJ |
457 | true |
458 | }, | |
459 | SMB2_COMMAND_WRITE => { | |
ecbf10da VJ |
460 | if r.nt_status == SMB_NTSTATUS_SUCCESS { |
461 | match parse_smb2_response_write(r.data) | |
462 | { | |
463 | IResult::Done(_, wr) => { | |
464 | SCLogDebug!("SMBv2: Write response => {:?}", wr); | |
465 | ||
466 | /* search key-guid map */ | |
467 | let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID, | |
468 | r.session_id, r.tree_id, r.message_id); | |
469 | let guid_vec = match state.ssn2vec_map.remove(&guid_key) { | |
470 | Some(p) => p, | |
471 | None => { | |
472 | SCLogDebug!("SMBv2 response: GUID NOT FOUND"); | |
473 | Vec::new() | |
474 | }, | |
475 | }; | |
476 | SCLogDebug!("SMBv2 write response for GUID {:?}", guid_vec); | |
477 | } | |
478 | _ => { | |
479 | events.push(SMBEvent::MalformedData); | |
480 | }, | |
75d7c9d6 | 481 | } |
75d7c9d6 VJ |
482 | } |
483 | false // the request may have created a generic tx, so handle that here | |
484 | }, | |
485 | SMB2_COMMAND_READ => { | |
486 | if r.nt_status == SMB_NTSTATUS_SUCCESS { | |
487 | smb2_read_response_record(state, &r); | |
488 | false | |
489 | ||
490 | } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE { | |
491 | SCLogDebug!("SMBv2: read response => EOF"); | |
492 | ||
493 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_OFFSET); | |
494 | let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) { | |
495 | Some(o) => o.guid, | |
496 | _ => { | |
497 | SCLogNotice!("SMBv2 READ response: reply to unknown request"); | |
498 | Vec::new() | |
499 | }, | |
500 | }; | |
fb986abe | 501 | let found = match state.get_file_tx_by_fuid(&file_guid, STREAM_TOCLIENT) { |
75d7c9d6 VJ |
502 | Some((tx, files, flags)) => { |
503 | if !tx.request_done { | |
504 | if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
505 | tdf.file_tracker.close(files, flags); | |
506 | } | |
507 | } | |
508 | tx.set_status(r.nt_status, false); | |
509 | tx.request_done = true; | |
510 | false | |
511 | }, | |
512 | None => { false }, | |
513 | }; | |
514 | if !found { | |
515 | SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid); | |
516 | } | |
517 | false | |
518 | } else { | |
519 | SCLogNotice!("SMBv2 READ: status {}", &smb_ntstatus_string(r.nt_status)); | |
520 | false | |
521 | } | |
522 | }, | |
523 | SMB2_COMMAND_CREATE => { | |
524 | if r.nt_status == SMB_NTSTATUS_SUCCESS { | |
525 | match parse_smb2_response_create(r.data) { | |
526 | IResult::Done(_, cr) => { | |
527 | SCLogDebug!("SMBv2: Create response => {:?}", cr); | |
528 | ||
529 | let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_FILENAME); | |
0e05ef73 VJ |
530 | if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) { |
531 | p.retain(|&i|i != 0x00); | |
532 | state.guid2name_map.insert(cr.guid.to_vec(), p); | |
533 | } else { | |
534 | SCLogDebug!("SMBv2 response: GUID NOT FOUND"); | |
535 | } | |
536 | ||
537 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); | |
538 | if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) { | |
539 | SCLogDebug!("tx {} with {}/{} marked as done", | |
540 | tx.id, r.command, &smb2_command_string(r.command)); | |
541 | tx.set_status(r.nt_status, false); | |
542 | tx.response_done = true; | |
543 | ||
544 | if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data { | |
545 | tdn.create_ts = cr.create_ts.as_unix(); | |
546 | tdn.last_access_ts = cr.last_access_ts.as_unix(); | |
547 | tdn.last_write_ts = cr.last_write_ts.as_unix(); | |
548 | tdn.last_change_ts = cr.last_change_ts.as_unix(); | |
549 | tdn.size = cr.size; | |
fb986abe | 550 | tdn.guid = cr.guid.to_vec(); |
0e05ef73 | 551 | } |
75d7c9d6 VJ |
552 | } |
553 | } | |
554 | _ => { | |
555 | events.push(SMBEvent::MalformedData); | |
556 | }, | |
557 | } | |
0e05ef73 VJ |
558 | true |
559 | } else { | |
560 | false | |
75d7c9d6 | 561 | } |
75d7c9d6 VJ |
562 | }, |
563 | SMB2_COMMAND_TREE_DISCONNECT => { | |
564 | // normally removed when processing request, | |
565 | // but in case we missed that try again here | |
566 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
567 | state.ssn2tree_map.remove(&tree_key); | |
568 | false | |
569 | } | |
570 | SMB2_COMMAND_TREE_CONNECT => { | |
571 | if r.nt_status == SMB_NTSTATUS_SUCCESS { | |
572 | match parse_smb2_response_tree_connect(r.data) { | |
573 | IResult::Done(_, tr) => { | |
574 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); | |
575 | let mut share_name = Vec::new(); | |
576 | let is_pipe = tr.share_type == 2; | |
577 | let found = match state.get_treeconnect_tx(name_key) { | |
578 | Some(tx) => { | |
579 | if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { | |
c56f5e11 | 580 | tdn.share_type = tr.share_type; |
75d7c9d6 VJ |
581 | tdn.is_pipe = is_pipe; |
582 | tdn.tree_id = r.tree_id as u32; | |
583 | share_name = tdn.share_name.to_vec(); | |
584 | } | |
585 | // update hdr now that we have a tree_id | |
586 | tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); | |
587 | tx.response_done = true; | |
588 | tx.set_status(r.nt_status, false); | |
589 | true | |
590 | }, | |
591 | None => { false }, | |
592 | }; | |
593 | if found { | |
594 | let tree = SMBTree::new(share_name.to_vec(), is_pipe); | |
595 | let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); | |
596 | state.ssn2tree_map.insert(tree_key, tree); | |
597 | } | |
598 | true | |
599 | } | |
600 | _ => { | |
601 | events.push(SMBEvent::MalformedData); | |
602 | false | |
603 | }, | |
604 | } | |
605 | } else { | |
606 | let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); | |
607 | let found = match state.get_treeconnect_tx(name_key) { | |
608 | Some(tx) => { | |
609 | tx.response_done = true; | |
610 | tx.set_status(r.nt_status, false); | |
611 | true | |
612 | }, | |
613 | None => { false }, | |
614 | }; | |
615 | found | |
616 | } | |
617 | }, | |
618 | SMB2_COMMAND_NEGOTIATE_PROTOCOL => { | |
6d56edc3 VJ |
619 | let res = if r.nt_status == SMB_NTSTATUS_SUCCESS { |
620 | parse_smb2_response_negotiate_protocol(r.data) | |
621 | } else { | |
622 | parse_smb2_response_negotiate_protocol_error(r.data) | |
623 | }; | |
624 | match res { | |
75d7c9d6 VJ |
625 | IResult::Done(_, rd) => { |
626 | SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect)); | |
627 | ||
628 | state.dialect = rd.dialect; | |
629 | let found2 = match state.get_negotiate_tx(2) { | |
630 | Some(tx) => { | |
6d56edc3 VJ |
631 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { |
632 | tdn.server_guid = rd.server_guid.to_vec(); | |
633 | } | |
75d7c9d6 VJ |
634 | tx.set_status(r.nt_status, false); |
635 | tx.response_done = true; | |
636 | true | |
637 | }, | |
638 | None => { false }, | |
639 | }; | |
640 | // SMB2 response to SMB1 request? | |
641 | let found1 = !found2 && match state.get_negotiate_tx(1) { | |
642 | Some(tx) => { | |
6d56edc3 VJ |
643 | if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { |
644 | tdn.server_guid = rd.server_guid.to_vec(); | |
645 | } | |
75d7c9d6 VJ |
646 | tx.set_status(r.nt_status, false); |
647 | tx.response_done = true; | |
648 | true | |
649 | }, | |
ecbf10da | 650 | None => { false }, |
75d7c9d6 VJ |
651 | }; |
652 | found1 || found2 | |
653 | }, | |
654 | _ => { | |
655 | events.push(SMBEvent::MalformedData); | |
656 | false | |
657 | } | |
658 | } | |
659 | }, | |
660 | _ => { | |
661 | SCLogDebug!("default case: no TX"); | |
662 | false | |
663 | }, | |
664 | }; | |
665 | if !have_tx { | |
283be3ca | 666 | let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); |
75d7c9d6 VJ |
667 | SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}", |
668 | &smb2_command_string(r.command), | |
283be3ca | 669 | r.session_id, r.tree_id, r.message_id); |
75d7c9d6 VJ |
670 | let _found = match state.get_generic_tx(2, r.command, &tx_hdr) { |
671 | Some(tx) => { | |
672 | SCLogDebug!("tx {} with {}/{} marked as done", | |
673 | tx.id, r.command, &smb2_command_string(r.command)); | |
674 | if r.nt_status != SMB_NTSTATUS_PENDING { | |
675 | tx.response_done = true; | |
676 | } | |
677 | tx.set_status(r.nt_status, false); | |
678 | tx.set_events(events); | |
679 | true | |
680 | }, | |
681 | _ => { | |
682 | SCLogNotice!("no tx found for {:?}", r); | |
683 | false | |
684 | }, | |
685 | }; | |
686 | } | |
687 | } |