]>
Commit | Line | Data |
---|---|---|
d6592211 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 | // written by Victor Julien | |
19 | // TCP buffering code written by Pierre Chifflier | |
20 | ||
21 | extern crate libc; | |
22 | use std; | |
23 | use std::mem::transmute; | |
d6592211 | 24 | use std::collections::{HashMap}; |
a306ccfd | 25 | use std::ffi::CStr; |
d6592211 | 26 | |
e0c6565e VJ |
27 | use nom::IResult; |
28 | ||
d6592211 | 29 | use log::*; |
e96d9c11 | 30 | use applayer; |
d6592211 VJ |
31 | use applayer::LoggerFlags; |
32 | use core::*; | |
33 | use filetracker::*; | |
34 | use filecontainer::*; | |
d6592211 VJ |
35 | |
36 | use nfs::types::*; | |
9edbb6f2 VJ |
37 | use nfs::rpc_records::*; |
38 | use nfs::nfs_records::*; | |
5153271b | 39 | use nfs::nfs2_records::*; |
9edbb6f2 | 40 | use nfs::nfs3_records::*; |
d6592211 | 41 | |
22e0fc97 | 42 | pub static mut SURICATA_NFS_FILE_CONFIG: Option<&'static SuricataFileContext> = None; |
d6592211 VJ |
43 | |
44 | /* | |
45 | * Record parsing. | |
46 | * | |
47 | * Incomplete records come in due to TCP splicing. For all record types | |
48 | * except READ and WRITE, processing only begins when the full record | |
49 | * is available. For READ/WRITE partial records are processed as well to | |
50 | * avoid queuing too much data. | |
51 | * | |
52 | * Getting file names. | |
53 | * | |
54 | * NFS makes heavy use of 'file handles' for operations. In many cases it | |
55 | * uses a file name just once and after that just the handle. For example, | |
56 | * if a client did a file listing (e.g. READDIRPLUS) and would READ the | |
57 | * file afterwards, the name will only appear in the READDIRPLUS answer. | |
58 | * To be able to log the names we store a mapping between file handles | |
0d79181d | 59 | * and file names in NFSState::namemap. |
d6592211 VJ |
60 | * |
61 | * Mapping NFS to Suricata's transaction model. | |
62 | * | |
63 | * The easiest way to do transactions would be to map each command/reply with | |
64 | * the same XID to a transaction. This would allow for per XID logging, detect | |
65 | * etc. However this model doesn't fit well with file tracking. The file | |
66 | * tracking in Suricata is really expecting to be one or more files to live | |
67 | * inside a single transaction. Would XID pairs be a transaction however, | |
68 | * there would be many transactions forming a single file. This will be very | |
69 | * inefficient. | |
70 | * | |
71 | * The model implemented here is as follows: each file transfer is a single | |
72 | * transaction. All XID pairs unrelated to those file transfers create | |
73 | * transactions per pair. | |
74 | * | |
75 | * A complicating factor is that the procedure matching is per tx, and a | |
76 | * file transfer may have multiple procedures involved. Currently now only | |
77 | * a COMMIT after WRITEs. A vector of additional procedures is kept to | |
78 | * match on this. | |
79 | * | |
80 | * File tracking | |
81 | * | |
82 | * Files are tracked per 'FileTransferTracker' and are stored in the | |
0d79181d | 83 | * NFSTransaction where they can be looked up per handle as part of the |
d6592211 VJ |
84 | * Transaction lookup. |
85 | */ | |
86 | ||
a306ccfd VJ |
87 | #[repr(u32)] |
88 | pub enum NFSEvent { | |
89 | MalformedData = 0, | |
350b5d99 NP |
90 | NonExistingVersion = 1, |
91 | UnsupportedVersion = 2, | |
a306ccfd VJ |
92 | } |
93 | ||
de7e0614 | 94 | #[derive(Debug)] |
0d79181d | 95 | pub enum NFSTransactionTypeData { |
de7e0614 | 96 | RENAME(Vec<u8>), |
0d79181d | 97 | FILE(NFSTransactionFile), |
75a6a137 VJ |
98 | } |
99 | ||
100 | #[derive(Debug)] | |
0d79181d | 101 | pub struct NFSTransactionFile { |
75a6a137 VJ |
102 | /// additional procedures part of a single file transfer. Currently |
103 | /// only COMMIT on WRITEs. | |
104 | pub file_additional_procs: Vec<u32>, | |
105 | ||
7c119cc5 VJ |
106 | pub chunk_count: u32, |
107 | ||
75a6a137 VJ |
108 | /// last xid of this file transfer. Last READ or COMMIT normally. |
109 | pub file_last_xid: u32, | |
110 | ||
111 | /// file tracker for a single file. Boxed so that we don't use | |
112 | /// as much space if we're not a file tx. | |
098aced7 | 113 | pub file_tracker: FileTransferTracker, |
75a6a137 VJ |
114 | } |
115 | ||
0d79181d VJ |
116 | impl NFSTransactionFile { |
117 | pub fn new() -> NFSTransactionFile { | |
118 | return NFSTransactionFile { | |
75a6a137 | 119 | file_additional_procs: Vec::new(), |
7c119cc5 | 120 | chunk_count:0, |
75a6a137 | 121 | file_last_xid: 0, |
098aced7 | 122 | file_tracker: FileTransferTracker::new(), |
75a6a137 VJ |
123 | } |
124 | } | |
de7e0614 | 125 | } |
d6592211 VJ |
126 | |
127 | #[derive(Debug)] | |
0d79181d | 128 | pub struct NFSTransaction { |
d6592211 | 129 | pub id: u64, /// internal id |
22e0fc97 | 130 | pub xid: u32, /// nfs req/reply pair id |
d6592211 | 131 | pub procedure: u32, |
de7e0614 VJ |
132 | /// file name of the object we're dealing with. In case of RENAME |
133 | /// this is the 'from' or original name. | |
d6592211 VJ |
134 | pub file_name: Vec<u8>, |
135 | ||
41376da0 | 136 | pub auth_type: u32, |
d6592211 VJ |
137 | pub request_machine_name: Vec<u8>, |
138 | pub request_uid: u32, | |
139 | pub request_gid: u32, | |
140 | ||
41376da0 VJ |
141 | pub rpc_response_status: u32, |
142 | pub nfs_response_status: u32, | |
d6592211 | 143 | |
d6592211 VJ |
144 | pub is_first: bool, |
145 | pub is_last: bool, | |
146 | ||
147 | /// for state tracking. false means this side is in progress, true | |
148 | /// that it's complete. | |
06f6c159 VJ |
149 | pub request_done: bool, |
150 | pub response_done: bool, | |
d6592211 | 151 | |
e0c6565e VJ |
152 | pub nfs_version: u16, |
153 | ||
d6592211 VJ |
154 | /// is a special file tx that we look up by file_handle instead of XID |
155 | pub is_file_tx: bool, | |
156 | /// file transactions are unidirectional in the sense that they track | |
157 | /// a single file on one direction | |
158 | pub file_tx_direction: u8, // STREAM_TOCLIENT or STREAM_TOSERVER | |
159 | pub file_handle: Vec<u8>, | |
160 | ||
75a6a137 | 161 | /// Procedure type specific data |
0d79181d | 162 | /// TODO see if this can be an Option<Box<NFSTransactionTypeData>>. Initial |
098aced7 | 163 | /// attempt failed. |
0d79181d | 164 | pub type_data: Option<NFSTransactionTypeData>, |
de7e0614 | 165 | |
8cda2a43 VJ |
166 | detect_flags_ts: u64, |
167 | detect_flags_tc: u64, | |
168 | ||
d6592211 VJ |
169 | pub logged: LoggerFlags, |
170 | pub de_state: Option<*mut DetectEngineState>, | |
171 | pub events: *mut AppLayerDecoderEvents, | |
172 | } | |
173 | ||
0d79181d VJ |
174 | impl NFSTransaction { |
175 | pub fn new() -> NFSTransaction { | |
176 | return NFSTransaction{ | |
d6592211 VJ |
177 | id: 0, |
178 | xid: 0, | |
179 | procedure: 0, | |
180 | file_name:Vec::new(), | |
181 | request_machine_name:Vec::new(), | |
182 | request_uid:0, | |
183 | request_gid:0, | |
41376da0 VJ |
184 | rpc_response_status:0, |
185 | nfs_response_status:0, | |
186 | auth_type: 0, | |
d6592211 VJ |
187 | is_first: false, |
188 | is_last: false, | |
189 | request_done: false, | |
190 | response_done: false, | |
e0c6565e | 191 | nfs_version:0, |
d6592211 VJ |
192 | is_file_tx: false, |
193 | file_tx_direction: 0, | |
194 | file_handle:Vec::new(), | |
de7e0614 | 195 | type_data: None, |
8cda2a43 VJ |
196 | detect_flags_ts: 0, |
197 | detect_flags_tc: 0, | |
d6592211 VJ |
198 | logged: LoggerFlags::new(), |
199 | de_state: None, | |
200 | events: std::ptr::null_mut(), | |
201 | } | |
202 | } | |
203 | ||
204 | pub fn free(&mut self) { | |
205 | if self.events != std::ptr::null_mut() { | |
206 | sc_app_layer_decoder_events_free_events(&mut self.events); | |
207 | } | |
7548944b VJ |
208 | match self.de_state { |
209 | Some(state) => { | |
210 | sc_detect_engine_state_free(state); | |
211 | } | |
212 | _ => {} | |
213 | } | |
f815027c VJ |
214 | } |
215 | } | |
216 | ||
217 | impl Drop for NFSTransaction { | |
218 | fn drop(&mut self) { | |
7548944b VJ |
219 | self.free(); |
220 | } | |
221 | } | |
222 | ||
d6592211 | 223 | #[derive(Debug)] |
0d79181d | 224 | pub struct NFSRequestXidMap { |
06f6c159 VJ |
225 | pub progver: u32, |
226 | pub procedure: u32, | |
227 | pub chunk_offset: u64, | |
228 | pub file_name:Vec<u8>, | |
d6592211 VJ |
229 | |
230 | /// READ replies can use this to get to the handle the request used | |
06f6c159 | 231 | pub file_handle:Vec<u8>, |
73d94fff VJ |
232 | |
233 | pub gssapi_proc: u32, | |
234 | pub gssapi_service: u32, | |
d6592211 VJ |
235 | } |
236 | ||
0d79181d VJ |
237 | impl NFSRequestXidMap { |
238 | pub fn new(progver: u32, procedure: u32, chunk_offset: u64) -> NFSRequestXidMap { | |
239 | NFSRequestXidMap { | |
5153271b VJ |
240 | progver:progver, |
241 | procedure:procedure, | |
242 | chunk_offset:chunk_offset, | |
d6592211 VJ |
243 | file_name:Vec::new(), |
244 | file_handle:Vec::new(), | |
73d94fff VJ |
245 | gssapi_proc: 0, |
246 | gssapi_service: 0, | |
d6592211 VJ |
247 | } |
248 | } | |
249 | } | |
250 | ||
251 | #[derive(Debug)] | |
0d79181d | 252 | pub struct NFSFiles { |
d6592211 VJ |
253 | pub files_ts: FileContainer, |
254 | pub files_tc: FileContainer, | |
255 | pub flags_ts: u16, | |
256 | pub flags_tc: u16, | |
257 | } | |
258 | ||
0d79181d VJ |
259 | impl NFSFiles { |
260 | pub fn new() -> NFSFiles { | |
261 | NFSFiles { | |
d6592211 VJ |
262 | files_ts:FileContainer::default(), |
263 | files_tc:FileContainer::default(), | |
264 | flags_ts:0, | |
265 | flags_tc:0, | |
266 | } | |
267 | } | |
268 | pub fn free(&mut self) { | |
269 | self.files_ts.free(); | |
270 | self.files_tc.free(); | |
271 | } | |
272 | ||
273 | pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16) | |
274 | { | |
275 | if direction == STREAM_TOSERVER { | |
276 | (&mut self.files_ts, self.flags_ts) | |
277 | } else { | |
278 | (&mut self.files_tc, self.flags_tc) | |
279 | } | |
280 | } | |
281 | } | |
282 | ||
283 | /// little wrapper around the FileTransferTracker::new_chunk method | |
06f6c159 | 284 | pub fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer, |
d6592211 VJ |
285 | flags: u16, name: &Vec<u8>, data: &[u8], |
286 | chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32) | |
287 | { | |
22e0fc97 | 288 | match unsafe {SURICATA_NFS_FILE_CONFIG} { |
098aced7 VJ |
289 | Some(sfcm) => { |
290 | ft.new_chunk(sfcm, files, flags, &name, data, chunk_offset, | |
291 | chunk_size, fill_bytes, is_last, xid); } | |
292 | None => panic!("BUG"), | |
d6592211 VJ |
293 | } |
294 | } | |
295 | ||
296 | #[derive(Debug)] | |
0d79181d | 297 | pub struct NFSState { |
d6592211 | 298 | /// map xid to procedure so replies can lookup the procedure |
0d79181d | 299 | pub requestmap: HashMap<u32, NFSRequestXidMap>, |
d6592211 VJ |
300 | |
301 | /// map file handle (1) to name (2) | |
302 | pub namemap: HashMap<Vec<u8>, Vec<u8>>, | |
303 | ||
304 | /// transactions list | |
0d79181d | 305 | pub transactions: Vec<NFSTransaction>, |
d6592211 VJ |
306 | |
307 | /// TCP segments defragmentation buffer | |
308 | pub tcp_buffer_ts: Vec<u8>, | |
309 | pub tcp_buffer_tc: Vec<u8>, | |
310 | ||
0d79181d | 311 | pub files: NFSFiles, |
d6592211 VJ |
312 | |
313 | /// partial record tracking | |
06f6c159 VJ |
314 | pub ts_chunk_xid: u32, |
315 | pub tc_chunk_xid: u32, | |
d6592211 | 316 | /// size of the current chunk that we still need to receive |
06f6c159 VJ |
317 | pub ts_chunk_left: u32, |
318 | pub tc_chunk_left: u32, | |
d6592211 | 319 | |
58af3913 VJ |
320 | ts_ssn_gap: bool, |
321 | tc_ssn_gap: bool, | |
322 | ||
8fe32f94 VJ |
323 | ts_gap: bool, // last TS update was gap |
324 | tc_gap: bool, // last TC update was gap | |
325 | ||
c7e10c73 VJ |
326 | is_udp: bool, |
327 | ||
aff576b5 VJ |
328 | pub nfs_version: u16, |
329 | ||
a306ccfd VJ |
330 | pub events: u16, |
331 | ||
d6592211 VJ |
332 | /// tx counter for assigning incrementing id's to tx's |
333 | tx_id: u64, | |
d6592211 VJ |
334 | } |
335 | ||
0d79181d | 336 | impl NFSState { |
d6592211 | 337 | /// Allocation function for a new TLS parser instance |
0d79181d VJ |
338 | pub fn new() -> NFSState { |
339 | NFSState { | |
d6592211 VJ |
340 | requestmap:HashMap::new(), |
341 | namemap:HashMap::new(), | |
342 | transactions: Vec::new(), | |
343 | tcp_buffer_ts:Vec::with_capacity(8192), | |
344 | tcp_buffer_tc:Vec::with_capacity(8192), | |
0d79181d | 345 | files:NFSFiles::new(), |
d6592211 VJ |
346 | ts_chunk_xid:0, |
347 | tc_chunk_xid:0, | |
348 | ts_chunk_left:0, | |
349 | tc_chunk_left:0, | |
58af3913 VJ |
350 | ts_ssn_gap:false, |
351 | tc_ssn_gap:false, | |
8fe32f94 VJ |
352 | ts_gap:false, |
353 | tc_gap:false, | |
c7e10c73 | 354 | is_udp:false, |
aff576b5 | 355 | nfs_version:0, |
a306ccfd | 356 | events:0, |
d6592211 | 357 | tx_id:0, |
d6592211 VJ |
358 | } |
359 | } | |
360 | pub fn free(&mut self) { | |
361 | self.files.free(); | |
362 | } | |
363 | ||
0d79181d VJ |
364 | pub fn new_tx(&mut self) -> NFSTransaction { |
365 | let mut tx = NFSTransaction::new(); | |
d6592211 VJ |
366 | self.tx_id += 1; |
367 | tx.id = self.tx_id; | |
368 | return tx; | |
369 | } | |
370 | ||
371 | pub fn free_tx(&mut self, tx_id: u64) { | |
372 | //SCLogNotice!("Freeing TX with ID {}", tx_id); | |
373 | let len = self.transactions.len(); | |
374 | let mut found = false; | |
375 | let mut index = 0; | |
376 | for i in 0..len { | |
377 | let tx = &self.transactions[i]; | |
378 | if tx.id == tx_id + 1 { | |
379 | found = true; | |
380 | index = i; | |
381 | break; | |
382 | } | |
383 | } | |
384 | if found { | |
385 | SCLogDebug!("freeing TX with ID {} at index {}", tx_id, index); | |
7548944b | 386 | self.transactions.remove(index); |
d6592211 VJ |
387 | } |
388 | } | |
389 | ||
0d79181d | 390 | pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NFSTransaction> { |
d6592211 VJ |
391 | SCLogDebug!("get_tx_by_id: tx_id={}", tx_id); |
392 | for tx in &mut self.transactions { | |
393 | if tx.id == tx_id + 1 { | |
0d79181d | 394 | SCLogDebug!("Found NFS TX with ID {}", tx_id); |
d6592211 VJ |
395 | return Some(tx); |
396 | } | |
397 | } | |
0d79181d | 398 | SCLogDebug!("Failed to find NFS TX with ID {}", tx_id); |
d6592211 VJ |
399 | return None; |
400 | } | |
401 | ||
0d79181d | 402 | pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFSTransaction> { |
d6592211 VJ |
403 | SCLogDebug!("get_tx_by_xid: tx_xid={}", tx_xid); |
404 | for tx in &mut self.transactions { | |
405 | if !tx.is_file_tx && tx.xid == tx_xid { | |
0d79181d | 406 | SCLogDebug!("Found NFS TX with ID {} XID {}", tx.id, tx.xid); |
d6592211 VJ |
407 | return Some(tx); |
408 | } | |
409 | } | |
0d79181d | 410 | SCLogDebug!("Failed to find NFS TX with XID {}", tx_xid); |
d6592211 VJ |
411 | return None; |
412 | } | |
413 | ||
e96d9c11 VJ |
414 | // for use with the C API call StateGetTxIterator |
415 | pub fn get_tx_iterator(&mut self, min_tx_id: u64, state: &mut u64) -> | |
416 | Option<(&NFSTransaction, u64, bool)> | |
417 | { | |
418 | let mut index = *state as usize; | |
419 | let len = self.transactions.len(); | |
420 | ||
421 | // find tx that is >= min_tx_id | |
422 | while index < len { | |
423 | let tx = &self.transactions[index]; | |
424 | if tx.id < min_tx_id + 1 { | |
425 | index += 1; | |
426 | continue; | |
427 | } | |
428 | *state = index as u64 + 1; | |
429 | SCLogDebug!("returning tx_id {} has_next? {} (len {} index {}), tx {:?}", | |
430 | tx.id - 1, (len - index) > 1, len, index, tx); | |
431 | return Some((tx, tx.id - 1, (len - index) > 1)); | |
432 | } | |
433 | return None; | |
434 | } | |
435 | ||
a306ccfd VJ |
436 | /// Set an event. The event is set on the most recent transaction. |
437 | pub fn set_event(&mut self, event: NFSEvent) { | |
438 | let len = self.transactions.len(); | |
439 | if len == 0 { | |
440 | return; | |
441 | } | |
442 | ||
fd38e5e8 | 443 | let tx = &mut self.transactions[len - 1]; |
a306ccfd VJ |
444 | sc_app_layer_decoder_events_set_event_raw(&mut tx.events, event as u8); |
445 | self.events += 1; | |
446 | } | |
447 | ||
d6592211 | 448 | // TODO maybe not enough users to justify a func |
9b42073e | 449 | pub fn mark_response_tx_done(&mut self, xid: u32, rpc_status: u32, nfs_status: u32, resp_handle: &Vec<u8>) |
d6592211 VJ |
450 | { |
451 | match self.get_tx_by_xid(xid) { | |
fd38e5e8 | 452 | Some(mytx) => { |
d6592211 | 453 | mytx.response_done = true; |
41376da0 VJ |
454 | mytx.rpc_response_status = rpc_status; |
455 | mytx.nfs_response_status = nfs_status; | |
db2d9281 VJ |
456 | if mytx.file_handle.len() == 0 && resp_handle.len() > 0 { |
457 | mytx.file_handle = resp_handle.to_vec(); | |
458 | } | |
d6592211 VJ |
459 | |
460 | SCLogDebug!("process_reply_record: tx ID {} XID {} REQUEST {} RESPONSE {}", | |
461 | mytx.id, mytx.xid, mytx.request_done, mytx.response_done); | |
462 | }, | |
463 | None => { | |
464 | //SCLogNotice!("process_reply_record: not TX found for XID {}", r.hdr.xid); | |
465 | }, | |
466 | } | |
d6592211 VJ |
467 | } |
468 | ||
9b42073e | 469 | pub fn process_request_record_lookup<'b>(&mut self, r: &RpcPacket<'b>, xidmap: &mut NFSRequestXidMap) { |
d6592211 VJ |
470 | match parse_nfs3_request_lookup(r.prog_data) { |
471 | IResult::Done(_, lookup) => { | |
472 | SCLogDebug!("LOOKUP {:?}", lookup); | |
473 | xidmap.file_name = lookup.name_vec; | |
474 | }, | |
f570905f | 475 | _ => { |
a306ccfd VJ |
476 | self.set_event(NFSEvent::MalformedData); |
477 | }, | |
d6592211 VJ |
478 | }; |
479 | } | |
480 | ||
06f6c159 | 481 | pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) { |
de7e0614 VJ |
482 | match self.namemap.get(&xidmap.file_handle) { |
483 | Some(n) => { | |
484 | SCLogDebug!("xidmap_handle2name: name {:?}", n); | |
485 | xidmap.file_name = n.to_vec(); | |
486 | }, | |
487 | _ => { | |
488 | SCLogDebug!("xidmap_handle2name: object {:?} not found", | |
489 | xidmap.file_handle); | |
490 | }, | |
491 | } | |
492 | } | |
493 | ||
d6592211 VJ |
494 | /// complete request record |
495 | fn process_request_record<'b>(&mut self, r: &RpcPacket<'b>) -> u32 { | |
496 | SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}", | |
497 | r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); | |
498 | ||
4c09766b VJ |
499 | match r.progver { |
500 | 4 => { | |
501 | self.process_request_record_v4(r) | |
502 | }, | |
503 | 3 => { | |
504 | self.process_request_record_v3(r) | |
505 | }, | |
506 | 2 => { | |
507 | self.process_request_record_v2(r) | |
508 | }, | |
509 | _ => { 1 }, | |
06f6c159 | 510 | } |
4c09766b VJ |
511 | } |
512 | ||
06f6c159 | 513 | pub fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8) |
0d79181d | 514 | -> (&mut NFSTransaction, &mut FileContainer, u16) |
d6592211 VJ |
515 | { |
516 | let mut tx = self.new_tx(); | |
517 | tx.file_name = file_name.to_vec(); | |
518 | tx.file_handle = file_handle.to_vec(); | |
519 | tx.is_file_tx = true; | |
520 | tx.file_tx_direction = direction; | |
521 | ||
0d79181d | 522 | tx.type_data = Some(NFSTransactionTypeData::FILE(NFSTransactionFile::new())); |
f570905f VJ |
523 | if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data { |
524 | d.file_tracker.tx_id = tx.id - 1; | |
d6592211 | 525 | } |
d6592211 VJ |
526 | SCLogDebug!("new_file_tx: TX FILE created: ID {} NAME {}", |
527 | tx.id, String::from_utf8_lossy(file_name)); | |
528 | self.transactions.push(tx); | |
529 | let tx_ref = self.transactions.last_mut(); | |
530 | let (files, flags) = self.files.get(direction); | |
531 | return (tx_ref.unwrap(), files, flags) | |
532 | } | |
533 | ||
06f6c159 | 534 | pub fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8) |
0d79181d | 535 | -> Option<(&mut NFSTransaction, &mut FileContainer, u16)> |
d6592211 VJ |
536 | { |
537 | let fh = file_handle.to_vec(); | |
538 | for tx in &mut self.transactions { | |
539 | if tx.is_file_tx && | |
540 | direction == tx.file_tx_direction && | |
541 | tx.file_handle == fh | |
542 | { | |
0d79181d | 543 | SCLogDebug!("Found NFS file TX with ID {} XID {}", tx.id, tx.xid); |
d6592211 VJ |
544 | let (files, flags) = self.files.get(direction); |
545 | return Some((tx, files, flags)); | |
546 | } | |
547 | } | |
0d79181d | 548 | SCLogDebug!("Failed to find NFS TX with handle {:?}", file_handle); |
d6592211 VJ |
549 | return None; |
550 | } | |
551 | ||
9b42073e | 552 | pub fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { |
d6592211 VJ |
553 | // for now assume that stable FILE_SYNC flags means a single chunk |
554 | let is_last = if w.stable == 2 { true } else { false }; | |
555 | ||
556 | let mut fill_bytes = 0; | |
557 | let pad = w.file_len % 4; | |
558 | if pad != 0 { | |
559 | fill_bytes = 4 - pad; | |
560 | } | |
561 | ||
562 | let file_handle = w.handle.value.to_vec(); | |
563 | let file_name = match self.namemap.get(w.handle.value) { | |
564 | Some(n) => { | |
565 | SCLogDebug!("WRITE name {:?}", n); | |
566 | n.to_vec() | |
567 | }, | |
568 | None => { | |
569 | SCLogDebug!("WRITE object {:?} not found", w.handle.value); | |
570 | Vec::new() | |
571 | }, | |
572 | }; | |
573 | ||
574 | let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOSERVER) { | |
575 | Some((tx, files, flags)) => { | |
f570905f VJ |
576 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
577 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
578 | &file_name, w.file_data, w.offset, | |
579 | w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); | |
580 | tdf.chunk_count += 1; | |
581 | if is_last { | |
582 | tdf.file_last_xid = r.hdr.xid; | |
583 | tx.is_last = true; | |
584 | tx.response_done = true; | |
585 | } | |
586 | true | |
587 | } else { | |
588 | false | |
589 | } | |
590 | }, | |
591 | None => { false }, | |
592 | }; | |
593 | if !found { | |
594 | let (tx, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOSERVER); | |
595 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
75a6a137 | 596 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
d6592211 VJ |
597 | &file_name, w.file_data, w.offset, |
598 | w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); | |
f570905f VJ |
599 | tx.procedure = NFSPROC3_WRITE; |
600 | tx.xid = r.hdr.xid; | |
601 | tx.is_first = true; | |
602 | tx.nfs_version = r.progver as u16; | |
d6592211 | 603 | if is_last { |
75a6a137 | 604 | tdf.file_last_xid = r.hdr.xid; |
d6592211 | 605 | tx.is_last = true; |
f570905f | 606 | tx.request_done = true; |
d6592211 | 607 | } |
d6592211 VJ |
608 | } |
609 | } | |
c7e10c73 VJ |
610 | if !self.is_udp { |
611 | self.ts_chunk_xid = r.hdr.xid; | |
612 | let file_data_len = w.file_data.len() as u32 - fill_bytes as u32; | |
613 | self.ts_chunk_left = w.file_len as u32 - file_data_len as u32; | |
614 | } | |
d6592211 VJ |
615 | 0 |
616 | } | |
617 | ||
618 | fn process_partial_write_request_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { | |
619 | SCLogDebug!("REQUEST {} procedure {} blob size {}", r.hdr.xid, r.procedure, r.prog_data.len()); | |
620 | ||
0d79181d | 621 | let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); |
d6592211 VJ |
622 | xidmap.file_handle = w.handle.value.to_vec(); |
623 | self.requestmap.insert(r.hdr.xid, xidmap); | |
624 | ||
625 | return self.process_write_record(r, w); | |
626 | } | |
627 | ||
5153271b VJ |
628 | fn process_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>) -> u32 { |
629 | let mut xidmap; | |
630 | match self.requestmap.remove(&r.hdr.xid) { | |
631 | Some(p) => { xidmap = p; }, | |
632 | _ => { | |
633 | SCLogDebug!("REPLY: xid {} NOT FOUND. GAPS? TS:{} TC:{}", | |
634 | r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap); | |
635 | ||
636 | // TODO we might be able to try to infer from the size + data | |
637 | // that this is a READ reply and pass the data to the file API anyway? | |
638 | return 0; | |
639 | }, | |
640 | } | |
641 | ||
aff576b5 VJ |
642 | if self.nfs_version == 0 { |
643 | self.nfs_version = xidmap.progver as u16; | |
644 | } | |
645 | ||
5153271b | 646 | match xidmap.progver { |
350b5d99 NP |
647 | 2 => { |
648 | SCLogDebug!("NFSv2 reply record"); | |
649 | return self.process_reply_record_v2(r, &xidmap); | |
650 | }, | |
5153271b VJ |
651 | 3 => { |
652 | SCLogDebug!("NFSv3 reply record"); | |
653 | return self.process_reply_record_v3(r, &mut xidmap); | |
654 | }, | |
350b5d99 | 655 | 4 => { |
06f6c159 VJ |
656 | SCLogDebug!("NFSv4 reply record"); |
657 | return self.process_reply_record_v4(r, &mut xidmap); | |
350b5d99 NP |
658 | }, |
659 | _ => { | |
660 | SCLogDebug!("Invalid NFS version"); | |
661 | self.set_event(NFSEvent::NonExistingVersion); | |
662 | return 0; | |
5153271b | 663 | }, |
5153271b VJ |
664 | } |
665 | } | |
666 | ||
d6592211 VJ |
667 | // update in progress chunks for file transfers |
668 | // return how much data we consumed | |
58af3913 | 669 | fn filetracker_update(&mut self, direction: u8, data: &[u8], gap_size: u32) -> u32 { |
d6592211 VJ |
670 | let mut chunk_left = if direction == STREAM_TOSERVER { |
671 | self.ts_chunk_left | |
672 | } else { | |
673 | self.tc_chunk_left | |
674 | }; | |
675 | if chunk_left == 0 { | |
676 | return 0 | |
677 | } | |
678 | let xid = if direction == STREAM_TOSERVER { | |
679 | self.ts_chunk_xid | |
680 | } else { | |
681 | self.tc_chunk_xid | |
682 | }; | |
683 | SCLogDebug!("chunk left {}, input {}", chunk_left, data.len()); | |
684 | ||
685 | let file_handle; | |
686 | // we have the data that we expect | |
687 | if chunk_left <= data.len() as u32 { | |
688 | chunk_left = 0; | |
689 | ||
690 | if direction == STREAM_TOSERVER { | |
691 | self.ts_chunk_xid = 0; | |
692 | ||
693 | // see if we have a file handle to work on | |
694 | match self.requestmap.get(&xid) { | |
695 | None => { | |
696 | SCLogDebug!("no file handle found for XID {:04X}", xid); | |
697 | return 0 | |
698 | }, | |
699 | Some(ref xidmap) => { | |
700 | file_handle = xidmap.file_handle.to_vec(); | |
701 | }, | |
702 | } | |
703 | } else { | |
704 | self.tc_chunk_xid = 0; | |
705 | ||
706 | // chunk done, remove requestmap entry | |
707 | match self.requestmap.remove(&xid) { | |
708 | None => { | |
709 | SCLogDebug!("no file handle found for XID {:04X}", xid); | |
710 | return 0 | |
711 | }, | |
712 | Some(xidmap) => { | |
713 | file_handle = xidmap.file_handle.to_vec(); | |
714 | }, | |
715 | } | |
716 | } | |
717 | } else { | |
718 | chunk_left -= data.len() as u32; | |
719 | ||
720 | // see if we have a file handle to work on | |
721 | match self.requestmap.get(&xid) { | |
722 | None => { | |
723 | SCLogDebug!("no file handle found for XID {:04X}", xid); | |
724 | return 0 }, | |
725 | Some(xidmap) => { | |
726 | file_handle = xidmap.file_handle.to_vec(); | |
727 | }, | |
728 | } | |
729 | } | |
730 | ||
731 | if direction == STREAM_TOSERVER { | |
732 | self.ts_chunk_left = chunk_left; | |
733 | } else { | |
734 | self.tc_chunk_left = chunk_left; | |
735 | } | |
736 | ||
58af3913 | 737 | let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap; |
d6592211 VJ |
738 | // get the tx and update it |
739 | let consumed = match self.get_file_tx_by_handle(&file_handle, direction) { | |
740 | Some((tx, files, flags)) => { | |
f570905f VJ |
741 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
742 | if ssn_gap { | |
743 | let queued_data = tdf.file_tracker.get_queued_size(); | |
744 | if queued_data > 2000000 { // TODO should probably be configurable | |
745 | SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data); | |
746 | tdf.file_tracker.trunc(files, flags); | |
747 | } | |
58af3913 | 748 | } |
58af3913 | 749 | |
f570905f VJ |
750 | tdf.chunk_count += 1; |
751 | let cs = tdf.file_tracker.update(files, flags, data, gap_size); | |
752 | /* see if we need to close the tx */ | |
753 | if tdf.file_tracker.is_done() { | |
754 | if direction == STREAM_TOCLIENT { | |
755 | tx.response_done = true; | |
756 | SCLogDebug!("TX {} response is done now that the file track is ready", tx.id); | |
757 | } else { | |
758 | tx.request_done = true; | |
759 | SCLogDebug!("TX {} request is done now that the file track is ready", tx.id); | |
760 | } | |
d27ed595 | 761 | } |
f570905f VJ |
762 | cs |
763 | } else { | |
764 | 0 | |
d27ed595 | 765 | } |
d6592211 VJ |
766 | }, |
767 | None => { 0 }, | |
768 | }; | |
769 | return consumed; | |
770 | } | |
771 | ||
772 | /// xidmapr is an Option as it's already removed from the map if we | |
773 | /// have a complete record. Otherwise we do a lookup ourselves. | |
06f6c159 | 774 | pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>, |
0d79181d | 775 | reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32 |
d6592211 VJ |
776 | { |
777 | let file_name; | |
778 | let file_handle; | |
779 | let chunk_offset; | |
5153271b | 780 | let nfs_version; |
d6592211 VJ |
781 | |
782 | match xidmapr { | |
783 | Some(xidmap) => { | |
784 | file_name = xidmap.file_name.to_vec(); | |
785 | file_handle = xidmap.file_handle.to_vec(); | |
786 | chunk_offset = xidmap.chunk_offset; | |
5153271b | 787 | nfs_version = xidmap.progver; |
d6592211 VJ |
788 | }, |
789 | None => { | |
f570905f VJ |
790 | if let Some(xidmap) = self.requestmap.get(&r.hdr.xid) { |
791 | file_name = xidmap.file_name.to_vec(); | |
792 | file_handle = xidmap.file_handle.to_vec(); | |
793 | chunk_offset = xidmap.chunk_offset; | |
794 | nfs_version = xidmap.progver; | |
795 | } else { | |
796 | return 0; | |
d6592211 VJ |
797 | } |
798 | }, | |
799 | } | |
06f6c159 | 800 | SCLogDebug!("chunk_offset {}", chunk_offset); |
d6592211 | 801 | |
5153271b | 802 | let mut is_last = reply.eof; |
d6592211 VJ |
803 | let mut fill_bytes = 0; |
804 | let pad = reply.count % 4; | |
805 | if pad != 0 { | |
806 | fill_bytes = 4 - pad; | |
807 | } | |
06f6c159 VJ |
808 | SCLogDebug!("XID {} is_last {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}", |
809 | r.hdr.xid, is_last, fill_bytes, reply.count, reply.data_len, reply.data.len()); | |
d6592211 | 810 | |
5153271b VJ |
811 | if nfs_version == 2 { |
812 | let size = match parse_nfs2_attribs(reply.attr_blob) { | |
813 | IResult::Done(_, ref attr) => { | |
814 | attr.asize | |
815 | }, | |
816 | _ => { 0 }, | |
817 | }; | |
818 | SCLogDebug!("NFSv2 READ reply record: File size {}. Offset {} data len {}: total {}", | |
819 | size, chunk_offset, reply.data_len, chunk_offset + reply.data_len as u64); | |
820 | ||
821 | if size as u64 == chunk_offset + reply.data_len as u64 { | |
822 | is_last = true; | |
823 | } | |
824 | ||
825 | } | |
826 | ||
d27ed595 VJ |
827 | let is_partial = reply.data.len() < reply.count as usize; |
828 | SCLogDebug!("partial data? {}", is_partial); | |
829 | ||
d6592211 VJ |
830 | let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOCLIENT) { |
831 | Some((tx, files, flags)) => { | |
06f6c159 | 832 | SCLogDebug!("updated TX {:?}", tx); |
f570905f VJ |
833 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
834 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, | |
835 | &file_name, reply.data, chunk_offset, | |
836 | reply.count, fill_bytes as u8, is_last, &r.hdr.xid); | |
837 | tdf.chunk_count += 1; | |
838 | if is_last { | |
839 | tdf.file_last_xid = r.hdr.xid; | |
840 | tx.rpc_response_status = r.reply_state; | |
841 | tx.nfs_response_status = reply.status; | |
842 | tx.is_last = true; | |
843 | tx.request_done = true; | |
844 | ||
845 | /* if this is a partial record we will close the tx | |
846 | * when we've received the final data */ | |
847 | if !is_partial { | |
848 | tx.response_done = true; | |
849 | SCLogDebug!("TX {} is DONE", tx.id); | |
850 | } | |
851 | } | |
852 | true | |
853 | } else { | |
854 | false | |
855 | } | |
856 | }, | |
857 | None => { false }, | |
858 | }; | |
859 | if !found { | |
860 | let (tx, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOCLIENT); | |
861 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { | |
75a6a137 | 862 | filetracker_newchunk(&mut tdf.file_tracker, files, flags, |
d6592211 | 863 | &file_name, reply.data, chunk_offset, |
e1e9ada9 | 864 | reply.count, fill_bytes as u8, is_last, &r.hdr.xid); |
f570905f VJ |
865 | tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ }; |
866 | tx.xid = r.hdr.xid; | |
867 | tx.is_first = true; | |
d6592211 | 868 | if is_last { |
75a6a137 | 869 | tdf.file_last_xid = r.hdr.xid; |
41376da0 VJ |
870 | tx.rpc_response_status = r.reply_state; |
871 | tx.nfs_response_status = reply.status; | |
d6592211 | 872 | tx.is_last = true; |
e1e9ada9 | 873 | tx.request_done = true; |
d27ed595 VJ |
874 | |
875 | /* if this is a partial record we will close the tx | |
876 | * when we've received the final data */ | |
877 | if !is_partial { | |
878 | tx.response_done = true; | |
879 | SCLogDebug!("TX {} is DONE", tx.id); | |
880 | } | |
d6592211 | 881 | } |
d6592211 VJ |
882 | } |
883 | } | |
884 | ||
c7e10c73 VJ |
885 | if !self.is_udp { |
886 | self.tc_chunk_xid = r.hdr.xid; | |
25edac76 | 887 | self.tc_chunk_left = (reply.count as u32 + fill_bytes) - reply.data.len() as u32; |
c7e10c73 | 888 | } |
d6592211 VJ |
889 | |
890 | SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {}", | |
891 | r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left); | |
892 | 0 | |
893 | } | |
894 | ||
9edbb6f2 | 895 | fn process_partial_read_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &NfsReplyRead<'b>) -> u32 { |
d6592211 VJ |
896 | SCLogDebug!("REPLY {} to procedure READ blob size {} / {}", |
897 | r.hdr.xid, r.prog_data.len(), reply.count); | |
898 | ||
899 | return self.process_read_record(r, reply, None); | |
900 | } | |
901 | ||
902 | fn peek_reply_record(&mut self, r: &RpcPacketHeader) -> u32 { | |
903 | let xidmap; | |
904 | match self.requestmap.get(&r.xid) { | |
905 | Some(p) => { xidmap = p; }, | |
906 | _ => { SCLogDebug!("REPLY: xid {} NOT FOUND", r.xid); return 0; }, | |
907 | } | |
908 | ||
909 | xidmap.procedure | |
910 | } | |
911 | ||
58af3913 VJ |
912 | pub fn parse_tcp_data_ts_gap<'b>(&mut self, gap_size: u32) -> u32 { |
913 | if self.tcp_buffer_ts.len() > 0 { | |
914 | self.tcp_buffer_ts.clear(); | |
915 | } | |
916 | let gap = vec![0; gap_size as usize]; | |
917 | let consumed = self.filetracker_update(STREAM_TOSERVER, &gap, gap_size); | |
918 | if consumed > gap_size { | |
f570905f VJ |
919 | SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); |
920 | return 1; | |
58af3913 VJ |
921 | } |
922 | self.ts_ssn_gap = true; | |
8fe32f94 | 923 | self.ts_gap = true; |
58af3913 VJ |
924 | return 0 |
925 | } | |
926 | ||
927 | pub fn parse_tcp_data_tc_gap<'b>(&mut self, gap_size: u32) -> u32 { | |
928 | if self.tcp_buffer_tc.len() > 0 { | |
929 | self.tcp_buffer_tc.clear(); | |
930 | } | |
931 | let gap = vec![0; gap_size as usize]; | |
932 | let consumed = self.filetracker_update(STREAM_TOCLIENT, &gap, gap_size); | |
933 | if consumed > gap_size { | |
f570905f VJ |
934 | SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); |
935 | return 1; | |
58af3913 VJ |
936 | } |
937 | self.tc_ssn_gap = true; | |
8fe32f94 | 938 | self.tc_gap = true; |
58af3913 VJ |
939 | return 0 |
940 | } | |
941 | ||
d6592211 VJ |
942 | /// Parsing function, handling TCP chunks fragmentation |
943 | pub fn parse_tcp_data_ts<'b>(&mut self, i: &'b[u8]) -> u32 { | |
944 | let mut v : Vec<u8>; | |
945 | let mut status = 0; | |
946 | SCLogDebug!("parse_tcp_data_ts ({})",i.len()); | |
947 | //SCLogDebug!("{:?}",i); | |
948 | // Check if TCP data is being defragmented | |
949 | let tcp_buffer = match self.tcp_buffer_ts.len() { | |
950 | 0 => i, | |
951 | _ => { | |
952 | v = self.tcp_buffer_ts.split_off(0); | |
953 | // sanity check vector length to avoid memory exhaustion | |
954 | if self.tcp_buffer_ts.len() + i.len() > 1000000 { | |
58af3913 | 955 | SCLogDebug!("parse_tcp_data_ts: TS buffer exploded {} {}", |
d6592211 VJ |
956 | self.tcp_buffer_ts.len(), i.len()); |
957 | return 1; | |
958 | }; | |
959 | v.extend_from_slice(i); | |
960 | v.as_slice() | |
961 | }, | |
962 | }; | |
963 | //SCLogDebug!("tcp_buffer ({})",tcp_buffer.len()); | |
964 | let mut cur_i = tcp_buffer; | |
965 | if cur_i.len() > 1000000 { | |
58af3913 | 966 | SCLogDebug!("BUG buffer exploded: {}", cur_i.len()); |
d6592211 | 967 | } |
d6592211 VJ |
968 | // take care of in progress file chunk transfers |
969 | // and skip buffer beyond it | |
58af3913 | 970 | let consumed = self.filetracker_update(STREAM_TOSERVER, cur_i, 0); |
d6592211 | 971 | if consumed > 0 { |
f570905f | 972 | if consumed > cur_i.len() as u32 { return 1; } |
d6592211 VJ |
973 | cur_i = &cur_i[consumed as usize..]; |
974 | } | |
8fe32f94 VJ |
975 | if self.ts_gap { |
976 | SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len()); | |
977 | ||
978 | let mut cnt = 0; | |
979 | while cur_i.len() > 0 { | |
980 | cnt += 1; | |
22e0fc97 | 981 | match nfs_probe(cur_i, STREAM_TOSERVER) { |
8fe32f94 VJ |
982 | 1 => { |
983 | SCLogDebug!("expected data found"); | |
984 | self.ts_gap = false; | |
985 | break; | |
986 | }, | |
987 | 0 => { | |
988 | SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", cur_i.len(), cnt); | |
989 | self.tcp_buffer_tc.extend_from_slice(cur_i); | |
990 | return 0; | |
991 | }, | |
992 | -1 => { | |
993 | cur_i = &cur_i[1..]; | |
994 | if cur_i.len() == 0 { | |
995 | SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", cnt); | |
996 | } | |
997 | }, | |
f570905f | 998 | _ => { return 1; }, |
8fe32f94 VJ |
999 | } |
1000 | } | |
1001 | SCLogDebug!("TS GAP handling done (input {})", cur_i.len()); | |
1002 | } | |
1003 | ||
d6592211 VJ |
1004 | while cur_i.len() > 0 { // min record size |
1005 | match parse_rpc_request_partial(cur_i) { | |
1006 | IResult::Done(_, ref rpc_phdr) => { | |
1007 | let rec_size = (rpc_phdr.hdr.frag_len + 4) as usize; | |
1008 | //SCLogDebug!("rec_size {}/{}", rec_size, cur_i.len()); | |
1009 | //SCLogDebug!("cur_i {:?}", cur_i); | |
1010 | ||
d6592211 VJ |
1011 | if rec_size > cur_i.len() { |
1012 | // special case: avoid buffering file write blobs | |
1013 | // as these can be large. | |
1014 | if rec_size >= 512 && cur_i.len() >= 44 { | |
1015 | // large record, likely file xfer | |
1016 | SCLogDebug!("large record {}, likely file xfer", rec_size); | |
1017 | ||
1018 | // quick peek, are in WRITE mode? | |
1019 | if rpc_phdr.procedure == NFSPROC3_WRITE { | |
1020 | SCLogDebug!("CONFIRMED WRITE: large record {}, file chunk xfer", rec_size); | |
1021 | ||
1022 | // lets try to parse the RPC record. Might fail with Incomplete. | |
1023 | match parse_rpc(cur_i) { | |
1024 | IResult::Done(remaining, ref rpc_record) => { | |
1025 | match parse_nfs3_request_write(rpc_record.prog_data) { | |
1026 | IResult::Done(_, ref nfs_request_write) => { | |
1027 | // deal with the partial nfs write data | |
1028 | status |= self.process_partial_write_request_record(rpc_record, nfs_request_write); | |
1029 | cur_i = remaining; // progress input past parsed record | |
1030 | }, | |
f570905f | 1031 | _ => { |
a306ccfd | 1032 | self.set_event(NFSEvent::MalformedData); |
d6592211 | 1033 | }, |
d6592211 VJ |
1034 | } |
1035 | }, | |
1036 | IResult::Incomplete(_) => { | |
1037 | // we just size checked for the minimal record size above, | |
1038 | // so if options are used (creds/verifier), we can still | |
1039 | // have Incomplete data. Fall through to the buffer code | |
1040 | // and try again on our next iteration. | |
1041 | SCLogDebug!("TS data incomplete"); | |
1042 | }, | |
f570905f VJ |
1043 | IResult::Error(_e) => { |
1044 | self.set_event(NFSEvent::MalformedData); | |
1045 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1046 | return 1; | |
1047 | }, | |
d6592211 VJ |
1048 | } |
1049 | } | |
1050 | } | |
1051 | self.tcp_buffer_ts.extend_from_slice(cur_i); | |
1052 | break; | |
1053 | } | |
1054 | ||
1055 | // we have the full records size worth of data, | |
1056 | // let's parse it | |
1057 | match parse_rpc(&cur_i[..rec_size]) { | |
1058 | IResult::Done(_, ref rpc_record) => { | |
1059 | cur_i = &cur_i[rec_size..]; | |
1060 | status |= self.process_request_record(rpc_record); | |
1061 | }, | |
a306ccfd VJ |
1062 | IResult::Incomplete(_) => { |
1063 | cur_i = &cur_i[rec_size..]; // progress input past parsed record | |
1064 | ||
1065 | // we shouldn't get incomplete as we have the full data | |
1066 | // so if we got incomplete anyway it's the data that is | |
1067 | // bad. | |
1068 | self.set_event(NFSEvent::MalformedData); | |
1069 | ||
1070 | status = 1; | |
d6592211 | 1071 | }, |
f570905f VJ |
1072 | IResult::Error(_e) => { |
1073 | self.set_event(NFSEvent::MalformedData); | |
1074 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1075 | return 1; | |
d6592211 VJ |
1076 | }, |
1077 | } | |
1078 | }, | |
1079 | IResult::Incomplete(_) => { | |
1080 | SCLogDebug!("Fragmentation required (TCP level) 2"); | |
1081 | self.tcp_buffer_ts.extend_from_slice(cur_i); | |
1082 | break; | |
1083 | }, | |
f570905f VJ |
1084 | IResult::Error(_e) => { |
1085 | self.set_event(NFSEvent::MalformedData); | |
1086 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1087 | return 1; | |
d6592211 VJ |
1088 | }, |
1089 | } | |
1090 | }; | |
1091 | status | |
1092 | } | |
1093 | ||
1094 | /// Parsing function, handling TCP chunks fragmentation | |
1095 | pub fn parse_tcp_data_tc<'b>(&mut self, i: &'b[u8]) -> u32 { | |
1096 | let mut v : Vec<u8>; | |
1097 | let mut status = 0; | |
1098 | SCLogDebug!("parse_tcp_data_tc ({})",i.len()); | |
1099 | //SCLogDebug!("{:?}",i); | |
1100 | // Check if TCP data is being defragmented | |
1101 | let tcp_buffer = match self.tcp_buffer_tc.len() { | |
1102 | 0 => i, | |
1103 | _ => { | |
1104 | v = self.tcp_buffer_tc.split_off(0); | |
1105 | // sanity check vector length to avoid memory exhaustion | |
1106 | if self.tcp_buffer_tc.len() + i.len() > 100000 { | |
1107 | SCLogDebug!("TC buffer exploded"); | |
1108 | return 1; | |
1109 | }; | |
58af3913 | 1110 | |
d6592211 VJ |
1111 | v.extend_from_slice(i); |
1112 | v.as_slice() | |
1113 | }, | |
1114 | }; | |
8fe32f94 | 1115 | SCLogDebug!("TC tcp_buffer ({}), input ({})",tcp_buffer.len(), i.len()); |
d6592211 VJ |
1116 | |
1117 | let mut cur_i = tcp_buffer; | |
1118 | if cur_i.len() > 100000 { | |
58af3913 | 1119 | SCLogDebug!("parse_tcp_data_tc: BUG buffer exploded {}", cur_i.len()); |
d6592211 VJ |
1120 | } |
1121 | ||
1122 | // take care of in progress file chunk transfers | |
1123 | // and skip buffer beyond it | |
58af3913 | 1124 | let consumed = self.filetracker_update(STREAM_TOCLIENT, cur_i, 0); |
d6592211 | 1125 | if consumed > 0 { |
f570905f | 1126 | if consumed > cur_i.len() as u32 { return 1; } |
d6592211 VJ |
1127 | cur_i = &cur_i[consumed as usize..]; |
1128 | } | |
8fe32f94 VJ |
1129 | if self.tc_gap { |
1130 | SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len()); | |
1131 | ||
1132 | let mut cnt = 0; | |
1133 | while cur_i.len() > 0 { | |
1134 | cnt += 1; | |
22e0fc97 | 1135 | match nfs_probe(cur_i, STREAM_TOCLIENT) { |
8fe32f94 VJ |
1136 | 1 => { |
1137 | SCLogDebug!("expected data found"); | |
1138 | self.tc_gap = false; | |
1139 | break; | |
1140 | }, | |
1141 | 0 => { | |
1142 | SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", cur_i.len(), cnt); | |
1143 | self.tcp_buffer_tc.extend_from_slice(cur_i); | |
1144 | return 0; | |
1145 | }, | |
1146 | -1 => { | |
1147 | cur_i = &cur_i[1..]; | |
1148 | if cur_i.len() == 0 { | |
1149 | SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", cnt); | |
1150 | } | |
1151 | }, | |
f570905f | 1152 | _ => { return 1; }, |
8fe32f94 VJ |
1153 | } |
1154 | } | |
1155 | SCLogDebug!("TC GAP handling done (input {})", cur_i.len()); | |
1156 | } | |
1157 | ||
d6592211 VJ |
1158 | while cur_i.len() > 0 { |
1159 | match parse_rpc_packet_header(cur_i) { | |
1160 | IResult::Done(_, ref rpc_hdr) => { | |
1161 | let rec_size = (rpc_hdr.frag_len + 4) as usize; | |
1162 | // see if we have all data available | |
1163 | if rec_size > cur_i.len() { | |
1164 | // special case: avoid buffering file read blobs | |
1165 | // as these can be large. | |
1166 | if rec_size >= 512 && cur_i.len() >= 128 {//36 { | |
1167 | // large record, likely file xfer | |
1168 | SCLogDebug!("large record {}, likely file xfer", rec_size); | |
1169 | ||
1170 | // quick peek, are in READ mode? | |
1171 | if self.peek_reply_record(&rpc_hdr) == NFSPROC3_READ { | |
1172 | SCLogDebug!("CONFIRMED large READ record {}, likely file chunk xfer", rec_size); | |
1173 | ||
1174 | // we should have enough data to parse the RPC record | |
1175 | match parse_rpc_reply(cur_i) { | |
1176 | IResult::Done(remaining, ref rpc_record) => { | |
1177 | match parse_nfs3_reply_read(rpc_record.prog_data) { | |
1178 | IResult::Done(_, ref nfs_reply_read) => { | |
1179 | // deal with the partial nfs read data | |
1180 | status |= self.process_partial_read_reply_record(rpc_record, nfs_reply_read); | |
1181 | cur_i = remaining; // progress input past parsed record | |
1182 | }, | |
1183 | IResult::Incomplete(_) => { | |
a306ccfd | 1184 | self.set_event(NFSEvent::MalformedData); |
d6592211 | 1185 | }, |
f570905f VJ |
1186 | IResult::Error(_e) => { |
1187 | self.set_event(NFSEvent::MalformedData); | |
1188 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1189 | return 1; | |
1190 | } | |
d6592211 VJ |
1191 | } |
1192 | }, | |
1193 | IResult::Incomplete(_) => { | |
1194 | // size check was done for MINIMAL record size, | |
1195 | // so Incomplete is normal. | |
1196 | SCLogDebug!("TC data incomplete"); | |
1197 | }, | |
f570905f VJ |
1198 | IResult::Error(_e) => { |
1199 | self.set_event(NFSEvent::MalformedData); | |
1200 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1201 | return 1; | |
1202 | } | |
d6592211 VJ |
1203 | } |
1204 | } | |
1205 | } | |
1206 | self.tcp_buffer_tc.extend_from_slice(cur_i); | |
1207 | break; | |
1208 | } | |
1209 | ||
1210 | // we have the full data of the record, lets parse | |
1211 | match parse_rpc_reply(&cur_i[..rec_size]) { | |
1212 | IResult::Done(_, ref rpc_record) => { | |
1213 | cur_i = &cur_i[rec_size..]; // progress input past parsed record | |
1214 | status |= self.process_reply_record(rpc_record); | |
1215 | }, | |
1216 | IResult::Incomplete(_) => { | |
a306ccfd VJ |
1217 | cur_i = &cur_i[rec_size..]; // progress input past parsed record |
1218 | ||
d6592211 | 1219 | // we shouldn't get incomplete as we have the full data |
a306ccfd VJ |
1220 | // so if we got incomplete anyway it's the data that is |
1221 | // bad. | |
1222 | self.set_event(NFSEvent::MalformedData); | |
1223 | ||
1224 | status = 1; | |
d6592211 | 1225 | }, |
f570905f VJ |
1226 | IResult::Error(_e) => { |
1227 | self.set_event(NFSEvent::MalformedData); | |
1228 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1229 | return 1; | |
1230 | } | |
d6592211 VJ |
1231 | } |
1232 | }, | |
1233 | IResult::Incomplete(_) => { | |
1234 | SCLogDebug!("REPLY: insufficient data for HDR"); | |
1235 | self.tcp_buffer_tc.extend_from_slice(cur_i); | |
1236 | break; | |
1237 | }, | |
f570905f VJ |
1238 | IResult::Error(_e) => { |
1239 | self.set_event(NFSEvent::MalformedData); | |
1240 | SCLogDebug!("Parsing failed: {:?}", _e); | |
1241 | return 1; | |
1242 | }, | |
d6592211 VJ |
1243 | } |
1244 | }; | |
1245 | status | |
1246 | } | |
c7e10c73 VJ |
1247 | /// Parsing function |
1248 | pub fn parse_udp_ts<'b>(&mut self, input: &'b[u8]) -> u32 { | |
1249 | let mut status = 0; | |
1250 | SCLogDebug!("parse_udp_ts ({})", input.len()); | |
1251 | if input.len() > 0 { | |
1252 | match parse_rpc_udp_request(input) { | |
1253 | IResult::Done(_, ref rpc_record) => { | |
1254 | self.is_udp = true; | |
5153271b VJ |
1255 | match rpc_record.progver { |
1256 | 3 => { | |
1257 | status |= self.process_request_record(rpc_record); | |
1258 | }, | |
1259 | 2 => { | |
1260 | status |= self.process_request_record_v2(rpc_record); | |
1261 | }, | |
f570905f | 1262 | _ => { status = 1; }, |
5153271b | 1263 | } |
c7e10c73 VJ |
1264 | }, |
1265 | IResult::Incomplete(_) => { | |
1266 | }, | |
f570905f | 1267 | IResult::Error(_e) => { SCLogDebug!("Parsing failed: {:?}", _e); |
c7e10c73 VJ |
1268 | }, |
1269 | } | |
1270 | } | |
1271 | status | |
1272 | } | |
1273 | ||
1274 | /// Parsing function | |
1275 | pub fn parse_udp_tc<'b>(&mut self, input: &'b[u8]) -> u32 { | |
1276 | let mut status = 0; | |
1277 | SCLogDebug!("parse_udp_tc ({})", input.len()); | |
1278 | if input.len() > 0 { | |
1279 | match parse_rpc_udp_reply(input) { | |
1280 | IResult::Done(_, ref rpc_record) => { | |
1281 | self.is_udp = true; | |
1282 | status |= self.process_reply_record(rpc_record); | |
1283 | }, | |
1284 | IResult::Incomplete(_) => { | |
1285 | }, | |
f570905f VJ |
1286 | IResult::Error(_e) => { |
1287 | SCLogDebug!("Parsing failed: {:?}", _e); | |
c7e10c73 VJ |
1288 | }, |
1289 | } | |
1290 | }; | |
1291 | status | |
1292 | } | |
d6592211 VJ |
1293 | fn getfiles(&mut self, direction: u8) -> * mut FileContainer { |
1294 | //SCLogDebug!("direction: {}", direction); | |
1295 | if direction == STREAM_TOCLIENT { | |
1296 | &mut self.files.files_tc as *mut FileContainer | |
1297 | } else { | |
1298 | &mut self.files.files_ts as *mut FileContainer | |
1299 | } | |
1300 | } | |
1301 | fn setfileflags(&mut self, direction: u8, flags: u16) { | |
1302 | SCLogDebug!("direction: {}, flags: {}", direction, flags); | |
1303 | if direction == 1 { | |
1304 | self.files.flags_tc = flags; | |
1305 | } else { | |
1306 | self.files.flags_ts = flags; | |
1307 | } | |
1308 | } | |
1309 | } | |
1310 | ||
0d79181d | 1311 | /// Returns *mut NFSState |
d6592211 | 1312 | #[no_mangle] |
22e0fc97 | 1313 | pub extern "C" fn rs_nfs_state_new() -> *mut libc::c_void { |
0d79181d | 1314 | let state = NFSState::new(); |
d6592211 VJ |
1315 | let boxed = Box::new(state); |
1316 | SCLogDebug!("allocating state"); | |
1317 | return unsafe{transmute(boxed)}; | |
1318 | } | |
1319 | ||
1320 | /// Params: | |
0d79181d | 1321 | /// - state: *mut NFSState as void pointer |
d6592211 | 1322 | #[no_mangle] |
22e0fc97 | 1323 | pub extern "C" fn rs_nfs_state_free(state: *mut libc::c_void) { |
d6592211 VJ |
1324 | // Just unbox... |
1325 | SCLogDebug!("freeing state"); | |
22e0fc97 VJ |
1326 | let mut nfs_state: Box<NFSState> = unsafe{transmute(state)}; |
1327 | nfs_state.free(); | |
d6592211 VJ |
1328 | } |
1329 | ||
e8939335 | 1330 | /// C binding parse a NFS TCP request. Returns 1 on success, -1 on failure. |
d6592211 | 1331 | #[no_mangle] |
e8939335 | 1332 | pub extern "C" fn rs_nfs_parse_request(_flow: *mut Flow, |
0d79181d | 1333 | state: &mut NFSState, |
d6592211 VJ |
1334 | _pstate: *mut libc::c_void, |
1335 | input: *mut libc::uint8_t, | |
1336 | input_len: libc::uint32_t, | |
1337 | _data: *mut libc::c_void) | |
1338 | -> libc::int8_t | |
1339 | { | |
1340 | let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; | |
1341 | SCLogDebug!("parsing {} bytes of request data", input_len); | |
58af3913 | 1342 | |
d6592211 VJ |
1343 | if state.parse_tcp_data_ts(buf) == 0 { |
1344 | 1 | |
1345 | } else { | |
1346 | -1 | |
1347 | } | |
1348 | } | |
1349 | ||
1350 | #[no_mangle] | |
e8939335 VJ |
1351 | pub extern "C" fn rs_nfs_parse_request_tcp_gap( |
1352 | state: &mut NFSState, | |
1353 | input_len: libc::uint32_t) | |
1354 | -> libc::int8_t | |
1355 | { | |
1356 | if state.parse_tcp_data_ts_gap(input_len as u32) == 0 { | |
1357 | return 1; | |
1358 | } | |
1359 | return -1; | |
1360 | } | |
1361 | ||
1362 | #[no_mangle] | |
1363 | pub extern "C" fn rs_nfs_parse_response(_flow: *mut Flow, | |
0d79181d | 1364 | state: &mut NFSState, |
d6592211 VJ |
1365 | _pstate: *mut libc::c_void, |
1366 | input: *mut libc::uint8_t, | |
1367 | input_len: libc::uint32_t, | |
1368 | _data: *mut libc::c_void) | |
1369 | -> libc::int8_t | |
1370 | { | |
1371 | SCLogDebug!("parsing {} bytes of response data", input_len); | |
1372 | let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; | |
58af3913 | 1373 | |
d6592211 VJ |
1374 | if state.parse_tcp_data_tc(buf) == 0 { |
1375 | 1 | |
1376 | } else { | |
1377 | -1 | |
1378 | } | |
1379 | } | |
1380 | ||
e8939335 VJ |
1381 | #[no_mangle] |
1382 | pub extern "C" fn rs_nfs_parse_response_tcp_gap( | |
1383 | state: &mut NFSState, | |
1384 | input_len: libc::uint32_t) | |
1385 | -> libc::int8_t | |
1386 | { | |
1387 | if state.parse_tcp_data_tc_gap(input_len as u32) == 0 { | |
1388 | return 1; | |
1389 | } | |
1390 | return -1; | |
1391 | } | |
1392 | ||
c7e10c73 VJ |
1393 | /// C binding parse a DNS request. Returns 1 on success, -1 on failure. |
1394 | #[no_mangle] | |
22e0fc97 | 1395 | pub extern "C" fn rs_nfs_parse_request_udp(_flow: *mut Flow, |
0d79181d | 1396 | state: &mut NFSState, |
c7e10c73 VJ |
1397 | _pstate: *mut libc::c_void, |
1398 | input: *mut libc::uint8_t, | |
1399 | input_len: libc::uint32_t, | |
1400 | _data: *mut libc::c_void) | |
1401 | -> libc::int8_t | |
1402 | { | |
1403 | let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; | |
1404 | SCLogDebug!("parsing {} bytes of request data", input_len); | |
1405 | ||
1406 | if state.parse_udp_ts(buf) == 0 { | |
1407 | 1 | |
1408 | } else { | |
1409 | -1 | |
1410 | } | |
1411 | } | |
1412 | ||
1413 | #[no_mangle] | |
22e0fc97 | 1414 | pub extern "C" fn rs_nfs_parse_response_udp(_flow: *mut Flow, |
0d79181d | 1415 | state: &mut NFSState, |
c7e10c73 VJ |
1416 | _pstate: *mut libc::c_void, |
1417 | input: *mut libc::uint8_t, | |
1418 | input_len: libc::uint32_t, | |
1419 | _data: *mut libc::c_void) | |
1420 | -> libc::int8_t | |
1421 | { | |
1422 | SCLogDebug!("parsing {} bytes of response data", input_len); | |
1423 | let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; | |
1424 | ||
1425 | if state.parse_udp_tc(buf) == 0 { | |
1426 | 1 | |
1427 | } else { | |
1428 | -1 | |
1429 | } | |
1430 | } | |
1431 | ||
d6592211 | 1432 | #[no_mangle] |
22e0fc97 | 1433 | pub extern "C" fn rs_nfs_state_get_tx_count(state: &mut NFSState) |
d6592211 VJ |
1434 | -> libc::uint64_t |
1435 | { | |
22e0fc97 | 1436 | SCLogDebug!("rs_nfs_state_get_tx_count: returning {}", state.tx_id); |
d6592211 VJ |
1437 | return state.tx_id; |
1438 | } | |
1439 | ||
1440 | #[no_mangle] | |
22e0fc97 | 1441 | pub extern "C" fn rs_nfs_state_get_tx(state: &mut NFSState, |
d6592211 | 1442 | tx_id: libc::uint64_t) |
0d79181d | 1443 | -> *mut NFSTransaction |
d6592211 VJ |
1444 | { |
1445 | match state.get_tx_by_id(tx_id) { | |
1446 | Some(tx) => { | |
1447 | return unsafe{transmute(tx)}; | |
1448 | } | |
1449 | None => { | |
1450 | return std::ptr::null_mut(); | |
1451 | } | |
1452 | } | |
1453 | } | |
1454 | ||
e96d9c11 VJ |
1455 | // for use with the C API call StateGetTxIterator |
1456 | #[no_mangle] | |
1457 | pub extern "C" fn rs_nfs_state_get_tx_iterator( | |
1458 | state: &mut NFSState, | |
1459 | min_tx_id: libc::uint64_t, | |
1460 | istate: &mut libc::uint64_t) | |
1461 | -> applayer::AppLayerGetTxIterTuple | |
1462 | { | |
1463 | match state.get_tx_iterator(min_tx_id, istate) { | |
1464 | Some((tx, out_tx_id, has_next)) => { | |
1465 | let c_tx = unsafe { transmute(tx) }; | |
1466 | let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); | |
1467 | return ires; | |
1468 | } | |
1469 | None => { | |
1470 | return applayer::AppLayerGetTxIterTuple::not_found(); | |
1471 | } | |
1472 | } | |
1473 | } | |
1474 | ||
d6592211 | 1475 | #[no_mangle] |
22e0fc97 | 1476 | pub extern "C" fn rs_nfs_state_tx_free(state: &mut NFSState, |
d6592211 VJ |
1477 | tx_id: libc::uint64_t) |
1478 | { | |
1479 | state.free_tx(tx_id); | |
1480 | } | |
1481 | ||
1482 | #[no_mangle] | |
22e0fc97 | 1483 | pub extern "C" fn rs_nfs_state_progress_completion_status( |
d6592211 VJ |
1484 | _direction: libc::uint8_t) |
1485 | -> libc::c_int | |
1486 | { | |
1487 | return 1; | |
1488 | } | |
1489 | ||
1490 | #[no_mangle] | |
22e0fc97 | 1491 | pub extern "C" fn rs_nfs_tx_get_alstate_progress(tx: &mut NFSTransaction, |
d6592211 VJ |
1492 | direction: libc::uint8_t) |
1493 | -> libc::uint8_t | |
1494 | { | |
1495 | if direction == STREAM_TOSERVER && tx.request_done { | |
1496 | //SCLogNotice!("TOSERVER progress 1"); | |
1497 | return 1; | |
1498 | } else if direction == STREAM_TOCLIENT && tx.response_done { | |
1499 | //SCLogNotice!("TOCLIENT progress 1"); | |
1500 | return 1; | |
1501 | } else { | |
1502 | //SCLogNotice!("{} progress 0", direction); | |
1503 | return 0; | |
1504 | } | |
1505 | } | |
1506 | ||
d6592211 | 1507 | #[no_mangle] |
22e0fc97 | 1508 | pub extern "C" fn rs_nfs_tx_set_logged(_state: &mut NFSState, |
0d79181d | 1509 | tx: &mut NFSTransaction, |
bca0cd71 | 1510 | logged: libc::uint32_t) |
d6592211 | 1511 | { |
bca0cd71 | 1512 | tx.logged.set(logged); |
d6592211 VJ |
1513 | } |
1514 | ||
1515 | #[no_mangle] | |
22e0fc97 | 1516 | pub extern "C" fn rs_nfs_tx_get_logged(_state: &mut NFSState, |
bca0cd71 VJ |
1517 | tx: &mut NFSTransaction) |
1518 | -> u32 | |
d6592211 | 1519 | { |
bca0cd71 | 1520 | return tx.logged.get(); |
d6592211 VJ |
1521 | } |
1522 | ||
d6592211 | 1523 | #[no_mangle] |
22e0fc97 | 1524 | pub extern "C" fn rs_nfs_state_set_tx_detect_state( |
0d79181d | 1525 | tx: &mut NFSTransaction, |
d6592211 VJ |
1526 | de_state: &mut DetectEngineState) |
1527 | { | |
d6592211 VJ |
1528 | tx.de_state = Some(de_state); |
1529 | } | |
1530 | ||
1531 | #[no_mangle] | |
22e0fc97 | 1532 | pub extern "C" fn rs_nfs_state_get_tx_detect_state( |
0d79181d | 1533 | tx: &mut NFSTransaction) |
d6592211 VJ |
1534 | -> *mut DetectEngineState |
1535 | { | |
1536 | match tx.de_state { | |
1537 | Some(ds) => { | |
7548944b | 1538 | SCLogDebug!("{}: getting de_state", tx.id); |
d6592211 VJ |
1539 | return ds; |
1540 | }, | |
1541 | None => { | |
7548944b | 1542 | SCLogDebug!("{}: getting de_state: have none", tx.id); |
d6592211 VJ |
1543 | return std::ptr::null_mut(); |
1544 | } | |
1545 | } | |
1546 | } | |
1547 | ||
8cda2a43 VJ |
1548 | #[no_mangle] |
1549 | pub extern "C" fn rs_nfs_tx_set_detect_flags( | |
1550 | tx: &mut NFSTransaction, | |
1551 | direction: libc::uint8_t, | |
1552 | flags: libc::uint64_t) | |
1553 | { | |
1554 | if (direction & STREAM_TOSERVER) != 0 { | |
1555 | tx.detect_flags_ts = flags as u64; | |
1556 | } else { | |
1557 | tx.detect_flags_tc = flags as u64; | |
1558 | } | |
1559 | } | |
1560 | ||
1561 | #[no_mangle] | |
1562 | pub extern "C" fn rs_nfs_tx_get_detect_flags( | |
1563 | tx: &mut NFSTransaction, | |
1564 | direction: libc::uint8_t) | |
1565 | -> libc::uint64_t | |
1566 | { | |
1567 | if (direction & STREAM_TOSERVER) != 0 { | |
1568 | return tx.detect_flags_ts as libc::uint64_t; | |
1569 | } else { | |
1570 | return tx.detect_flags_tc as libc::uint64_t; | |
1571 | } | |
1572 | } | |
1573 | ||
a306ccfd VJ |
1574 | #[no_mangle] |
1575 | pub extern "C" fn rs_nfs_state_get_events(state: &mut NFSState, | |
1576 | tx_id: libc::uint64_t) | |
1577 | -> *mut AppLayerDecoderEvents | |
1578 | { | |
1579 | match state.get_tx_by_id(tx_id) { | |
1580 | Some(tx) => { | |
1581 | return tx.events; | |
1582 | } | |
1583 | _ => { | |
1584 | return std::ptr::null_mut(); | |
1585 | } | |
1586 | } | |
1587 | } | |
1588 | ||
1589 | #[no_mangle] | |
1590 | pub extern "C" fn rs_nfs_state_get_event_info(event_name: *const libc::c_char, | |
1591 | event_id: *mut libc::c_int, | |
1592 | event_type: *mut AppLayerEventType) | |
1593 | -> i8 | |
1594 | { | |
1595 | if event_name == std::ptr::null() { | |
1596 | return -1; | |
1597 | } | |
1598 | let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) }; | |
1599 | let event = match c_event_name.to_str() { | |
1600 | Ok(s) => { | |
1601 | match s { | |
1602 | "malformed_data" => NFSEvent::MalformedData as i32, | |
1603 | _ => -1, // unknown event | |
1604 | } | |
1605 | }, | |
1606 | Err(_) => -1, // UTF-8 conversion failed | |
1607 | }; | |
1608 | unsafe{ | |
1609 | *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION; | |
1610 | *event_id = event as libc::c_int; | |
1611 | }; | |
1612 | 0 | |
1613 | } | |
1614 | ||
d6592211 VJ |
1615 | /// return procedure(s) in the tx. At 0 return the main proc, |
1616 | /// otherwise get procs from the 'file_additional_procs'. | |
1617 | /// Keep calling until 0 is returned. | |
1618 | #[no_mangle] | |
22e0fc97 VJ |
1619 | pub extern "C" fn rs_nfs_tx_get_procedures(tx: &mut NFSTransaction, |
1620 | i: libc::uint16_t, | |
1621 | procedure: *mut libc::uint32_t) | |
1622 | -> libc::uint8_t | |
d6592211 VJ |
1623 | { |
1624 | if i == 0 { | |
1625 | unsafe { | |
1626 | *procedure = tx.procedure as libc::uint32_t; | |
1627 | } | |
1628 | return 1; | |
1629 | } | |
1630 | ||
75a6a137 VJ |
1631 | if !tx.is_file_tx { |
1632 | return 0; | |
1633 | } | |
1634 | ||
1635 | /* file tx handling follows */ | |
1636 | ||
f570905f VJ |
1637 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
1638 | let idx = i as usize - 1; | |
1639 | if idx < tdf.file_additional_procs.len() { | |
1640 | let p = tdf.file_additional_procs[idx]; | |
1641 | unsafe { | |
1642 | *procedure = p as libc::uint32_t; | |
1643 | } | |
1644 | return 1; | |
d6592211 | 1645 | } |
d6592211 VJ |
1646 | } |
1647 | return 0; | |
1648 | } | |
1649 | ||
e0c6565e VJ |
1650 | #[no_mangle] |
1651 | pub extern "C" fn rs_nfs_tx_get_version(tx: &mut NFSTransaction, | |
22e0fc97 | 1652 | version: *mut libc::uint32_t) |
e0c6565e VJ |
1653 | { |
1654 | unsafe { | |
1655 | *version = tx.nfs_version as libc::uint32_t; | |
1656 | } | |
1657 | } | |
1658 | ||
d6592211 | 1659 | #[no_mangle] |
22e0fc97 | 1660 | pub extern "C" fn rs_nfs_init(context: &'static mut SuricataFileContext) |
d6592211 VJ |
1661 | { |
1662 | unsafe { | |
22e0fc97 | 1663 | SURICATA_NFS_FILE_CONFIG = Some(context); |
d6592211 VJ |
1664 | } |
1665 | } | |
1666 | ||
22e0fc97 | 1667 | pub fn nfs_probe(i: &[u8], direction: u8) -> i8 { |
8fe32f94 VJ |
1668 | if direction == STREAM_TOCLIENT { |
1669 | match parse_rpc_reply(i) { | |
1670 | IResult::Done(_, ref rpc) => { | |
1671 | if rpc.hdr.frag_len >= 24 && rpc.hdr.frag_len <= 35000 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { | |
5153271b | 1672 | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); |
8fe32f94 VJ |
1673 | return 1; |
1674 | } else { | |
1675 | return -1; | |
1676 | } | |
1677 | }, | |
1678 | IResult::Incomplete(_) => { | |
1679 | match parse_rpc_packet_header (i) { | |
1680 | IResult::Done(_, ref rpc_hdr) => { | |
1681 | if rpc_hdr.frag_len >= 24 && rpc_hdr.frag_len <= 35000 && rpc_hdr.xid != 0 && rpc_hdr.msgtype == 1 { | |
5153271b | 1682 | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype); |
8fe32f94 VJ |
1683 | return 1; |
1684 | } else { | |
1685 | return -1; | |
1686 | } | |
1687 | }, | |
1688 | IResult::Incomplete(_) => { }, | |
1689 | IResult::Error(_) => { | |
1690 | return -1; | |
1691 | }, | |
1692 | } | |
1693 | ||
1694 | ||
1695 | return 0; | |
1696 | }, | |
1697 | IResult::Error(_) => { | |
1698 | return -1; | |
1699 | }, | |
1700 | } | |
1701 | } else { | |
1702 | match parse_rpc(i) { | |
1703 | IResult::Done(_, ref rpc) => { | |
82bd732f | 1704 | if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 && |
06f6c159 VJ |
1705 | rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) && |
1706 | rpc.program == 100003 && | |
82bd732f VJ |
1707 | rpc.procedure <= NFSPROC3_COMMIT |
1708 | { | |
8fe32f94 VJ |
1709 | return 1; |
1710 | } else { | |
1711 | return -1; | |
1712 | } | |
1713 | }, | |
1714 | IResult::Incomplete(_) => { | |
1715 | return 0; | |
1716 | }, | |
1717 | IResult::Error(_) => { | |
1718 | return -1; | |
1719 | }, | |
1720 | } | |
1721 | } | |
1722 | } | |
1723 | ||
22e0fc97 | 1724 | pub fn nfs_probe_udp(i: &[u8], direction: u8) -> i8 { |
c7e10c73 VJ |
1725 | if direction == STREAM_TOCLIENT { |
1726 | match parse_rpc_udp_reply(i) { | |
1727 | IResult::Done(_, ref rpc) => { | |
1728 | if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { | |
5153271b | 1729 | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); |
c7e10c73 VJ |
1730 | return 1; |
1731 | } else { | |
1732 | return -1; | |
1733 | } | |
1734 | }, | |
1735 | IResult::Incomplete(_) => { | |
1736 | return -1; | |
1737 | }, | |
1738 | IResult::Error(_) => { | |
1739 | return -1; | |
1740 | }, | |
1741 | } | |
1742 | } else { | |
1743 | match parse_rpc_udp_request(i) { | |
1744 | IResult::Done(_, ref rpc) => { | |
1745 | if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 { | |
1746 | return 1; | |
5153271b VJ |
1747 | } else if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 2 && rpc.program == 100003 { |
1748 | SCLogDebug!("NFSv2!"); | |
1749 | return 1; | |
c7e10c73 VJ |
1750 | } else { |
1751 | return -1; | |
1752 | } | |
1753 | }, | |
1754 | IResult::Incomplete(_) => { | |
1755 | return -1; | |
1756 | }, | |
1757 | IResult::Error(_) => { | |
1758 | return -1; | |
1759 | }, | |
1760 | } | |
1761 | } | |
1762 | } | |
1763 | ||
d6592211 VJ |
1764 | /// TOSERVER probe function |
1765 | #[no_mangle] | |
d9f87cec | 1766 | pub extern "C" fn rs_nfs_probe_ts(input: *const libc::uint8_t, len: libc::uint32_t) |
d6592211 VJ |
1767 | -> libc::int8_t |
1768 | { | |
1769 | let slice: &[u8] = unsafe { | |
1770 | std::slice::from_raw_parts(input as *mut u8, len as usize) | |
1771 | }; | |
22e0fc97 | 1772 | return nfs_probe(slice, STREAM_TOSERVER); |
d6592211 | 1773 | } |
22e0fc97 | 1774 | |
d9f87cec VJ |
1775 | /// TOCLIENT probe function |
1776 | #[no_mangle] | |
1777 | pub extern "C" fn rs_nfs_probe_tc(input: *const libc::uint8_t, len: libc::uint32_t) | |
1778 | -> libc::int8_t | |
1779 | { | |
1780 | let slice: &[u8] = unsafe { | |
1781 | std::slice::from_raw_parts(input as *mut u8, len as usize) | |
1782 | }; | |
22e0fc97 | 1783 | return nfs_probe(slice, STREAM_TOCLIENT); |
d9f87cec VJ |
1784 | } |
1785 | ||
c7e10c73 VJ |
1786 | /// TOSERVER probe function |
1787 | #[no_mangle] | |
1788 | pub extern "C" fn rs_nfs_probe_udp_ts(input: *const libc::uint8_t, len: libc::uint32_t) | |
1789 | -> libc::int8_t | |
1790 | { | |
1791 | let slice: &[u8] = unsafe { | |
1792 | std::slice::from_raw_parts(input as *mut u8, len as usize) | |
1793 | }; | |
22e0fc97 | 1794 | return nfs_probe_udp(slice, STREAM_TOSERVER); |
c7e10c73 | 1795 | } |
22e0fc97 | 1796 | |
c7e10c73 VJ |
1797 | /// TOCLIENT probe function |
1798 | #[no_mangle] | |
1799 | pub extern "C" fn rs_nfs_probe_udp_tc(input: *const libc::uint8_t, len: libc::uint32_t) | |
1800 | -> libc::int8_t | |
1801 | { | |
1802 | let slice: &[u8] = unsafe { | |
1803 | std::slice::from_raw_parts(input as *mut u8, len as usize) | |
1804 | }; | |
22e0fc97 | 1805 | return nfs_probe_udp(slice, STREAM_TOCLIENT); |
c7e10c73 | 1806 | } |
d6592211 VJ |
1807 | |
1808 | #[no_mangle] | |
22e0fc97 | 1809 | pub extern "C" fn rs_nfs_getfiles(direction: u8, ptr: *mut NFSState) -> * mut FileContainer { |
d6592211 VJ |
1810 | if ptr.is_null() { panic!("NULL ptr"); }; |
1811 | let parser = unsafe { &mut *ptr }; | |
1812 | parser.getfiles(direction) | |
1813 | } | |
1814 | #[no_mangle] | |
22e0fc97 | 1815 | pub extern "C" fn rs_nfs_setfileflags(direction: u8, ptr: *mut NFSState, flags: u16) { |
d6592211 VJ |
1816 | if ptr.is_null() { panic!("NULL ptr"); }; |
1817 | let parser = unsafe { &mut *ptr }; | |
1818 | SCLogDebug!("direction {} flags {}", direction, flags); | |
1819 | parser.setfileflags(direction, flags) | |
1820 | } |