/src/suricata7/rust/src/nfs/nfs.rs
Line | Count | Source |
1 | | /* Copyright (C) 2017-2021 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 | | |
20 | | use std; |
21 | | use std::cmp; |
22 | | use std::collections::HashMap; |
23 | | use std::ffi::CString; |
24 | | |
25 | | use nom7::{Err, Needed}; |
26 | | |
27 | | use crate::applayer; |
28 | | use crate::applayer::*; |
29 | | use crate::frames::*; |
30 | | use crate::core::*; |
31 | | use crate::conf::*; |
32 | | use crate::filetracker::*; |
33 | | use crate::filecontainer::*; |
34 | | |
35 | | use crate::nfs::types::*; |
36 | | use crate::nfs::rpc_records::*; |
37 | | use crate::nfs::nfs_records::*; |
38 | | use crate::nfs::nfs2_records::*; |
39 | | use crate::nfs::nfs3_records::*; |
40 | | |
41 | | pub static mut SURICATA_NFS_FILE_CONFIG: Option<&'static SuricataFileContext> = None; |
42 | | |
43 | | pub const NFS_MIN_FRAME_LEN: u16 = 32; |
44 | | |
45 | | static mut NFS_MAX_TX: usize = 1024; |
46 | | |
47 | | pub const RPC_TCP_PRE_CREDS: usize = 28; |
48 | | pub const RPC_UDP_PRE_CREDS: usize = 24; |
49 | | |
50 | | static mut ALPROTO_NFS: AppProto = ALPROTO_UNKNOWN; |
51 | | /* |
52 | | * Record parsing. |
53 | | * |
54 | | * Incomplete records come in due to TCP splicing. For all record types |
55 | | * except READ and WRITE, processing only begins when the full record |
56 | | * is available. For READ/WRITE partial records are processed as well to |
57 | | * avoid queuing too much data. |
58 | | * |
59 | | * Getting file names. |
60 | | * |
61 | | * NFS makes heavy use of 'file handles' for operations. In many cases it |
62 | | * uses a file name just once and after that just the handle. For example, |
63 | | * if a client did a file listing (e.g. READDIRPLUS) and would READ the |
64 | | * file afterwards, the name will only appear in the READDIRPLUS answer. |
65 | | * To be able to log the names we store a mapping between file handles |
66 | | * and file names in NFSState::namemap. |
67 | | * |
68 | | * Mapping NFS to Suricata's transaction model. |
69 | | * |
70 | | * The easiest way to do transactions would be to map each command/reply with |
71 | | * the same XID to a transaction. This would allow for per XID logging, detect |
72 | | * etc. However this model doesn't fit well with file tracking. The file |
73 | | * tracking in Suricata is really expecting to be one or more files to live |
74 | | * inside a single transaction. Would XID pairs be a transaction however, |
75 | | * there would be many transactions forming a single file. This will be very |
76 | | * inefficient. |
77 | | * |
78 | | * The model implemented here is as follows: each file transfer is a single |
79 | | * transaction. All XID pairs unrelated to those file transfers create |
80 | | * transactions per pair. |
81 | | * |
82 | | * A complicating factor is that the procedure matching is per tx, and a |
83 | | * file transfer may have multiple procedures involved. Currently now only |
84 | | * a COMMIT after WRITEs. A vector of additional procedures is kept to |
85 | | * match on this. |
86 | | * |
87 | | * File tracking |
88 | | * |
89 | | * Files are tracked per 'FileTransferTracker' and are stored in the |
90 | | * NFSTransaction where they can be looked up per handle as part of the |
91 | | * Transaction lookup. |
92 | | */ |
93 | | |
94 | | pub static mut NFS_CFG_MAX_REQ: usize = 512; |
95 | | pub static mut NFS_CFG_MAX_NAMES: usize = 512; |
96 | | |
97 | | #[derive(AppLayerFrameType)] |
98 | | pub enum NFSFrameType { |
99 | | RPCPdu, |
100 | | RPCHdr, |
101 | | RPCData, |
102 | | RPCCreds, // for rpc calls | rpc.creds [creds_flavor + creds_len + creds] |
103 | | |
104 | | NFSPdu, |
105 | | NFSStatus, |
106 | | |
107 | | NFS4Pdu, |
108 | | NFS4Hdr, |
109 | | NFS4Ops, |
110 | | NFS4Status, |
111 | | } |
112 | | |
113 | | #[derive(FromPrimitive, Debug, AppLayerEvent)] |
114 | | pub enum NFSEvent { |
115 | | MalformedData = 0, |
116 | | NonExistingVersion = 1, |
117 | | UnsupportedVersion = 2, |
118 | | TooManyTransactions = 3, |
119 | | } |
120 | | |
121 | | #[derive(Debug)] |
122 | | pub enum NFSTransactionTypeData { |
123 | | RENAME(Vec<u8>), |
124 | | FILE(NFSTransactionFile), |
125 | | } |
126 | | |
127 | | #[derive(Default, Debug)] |
128 | | pub struct NFSTransactionFile { |
129 | | /// file transactions are unidirectional in the sense that they track |
130 | | /// a single file on one direction |
131 | | pub direction: Direction, // Direction::ToClient or Direction::ToServer |
132 | | |
133 | | /// additional procedures part of a single file transfer. Currently |
134 | | /// only COMMIT on WRITEs. |
135 | | pub file_additional_procs: Vec<u32>, |
136 | | |
137 | | pub chunk_count: u32, |
138 | | |
139 | | /// last xid of this file transfer. Last READ or COMMIT normally. |
140 | | pub file_last_xid: u32, |
141 | | |
142 | | /// after a gap, this will be set to a time in the future. If the file |
143 | | /// receives no updates before that, it will be considered complete. |
144 | | pub post_gap_ts: u64, |
145 | | |
146 | | /// file tracker for a single file. Boxed so that we don't use |
147 | | /// as much space if we're not a file tx. |
148 | | pub file_tracker: FileTransferTracker, |
149 | | } |
150 | | |
151 | | impl NFSTransactionFile { |
152 | 48.0k | pub fn new() -> Self { |
153 | 48.0k | return Self { |
154 | 48.0k | file_tracker: FileTransferTracker::new(), |
155 | 48.0k | ..Default::default() |
156 | 48.0k | } |
157 | 48.0k | } |
158 | 318k | pub fn update_file_flags(&mut self, flow_file_flags: u16) { |
159 | 318k | let dir_flag = if self.direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; |
160 | 318k | self.file_tracker.file_flags = unsafe { FileFlowFlagsToFlags(flow_file_flags, dir_flag) }; |
161 | 318k | } |
162 | | } |
163 | | |
164 | | #[no_mangle] |
165 | 35.5M | pub unsafe extern "C" fn rs_nfs_gettxfiles(_state: *mut std::ffi::c_void, tx: *mut std::ffi::c_void, direction: u8) -> AppLayerGetFileState { |
166 | 35.5M | let tx = cast_pointer!(tx, NFSTransaction); |
167 | 35.5M | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
168 | 35.5M | let tx_dir : u8 = tdf.direction.into(); |
169 | 35.5M | if direction & tx_dir != 0 { |
170 | 35.5M | if let Some(sfcm) = { SURICATA_NFS_FILE_CONFIG } { |
171 | 35.5M | return AppLayerGetFileState { fc: &mut tdf.file_tracker.file, cfg: sfcm.files_sbcfg } |
172 | 0 | } |
173 | 10.7k | } |
174 | 5.28k | } |
175 | 16.0k | AppLayerGetFileState::err() |
176 | 35.5M | } |
177 | | |
178 | | #[derive(Debug)] |
179 | | pub struct NFSTransaction { |
180 | | pub id: u64, /// internal id |
181 | | pub xid: u32, /// nfs req/reply pair id |
182 | | pub procedure: u32, |
183 | | /// file name of the object we're dealing with. In case of RENAME |
184 | | /// this is the 'from' or original name. |
185 | | pub file_name: Vec<u8>, |
186 | | |
187 | | pub auth_type: u32, |
188 | | pub request_machine_name: Vec<u8>, |
189 | | pub request_uid: u32, |
190 | | pub request_gid: u32, |
191 | | |
192 | | pub rpc_response_status: u32, |
193 | | pub nfs_response_status: u32, |
194 | | |
195 | | pub is_first: bool, |
196 | | pub is_last: bool, |
197 | | |
198 | | /// for state tracking. false means this side is in progress, true |
199 | | /// that it's complete. |
200 | | pub request_done: bool, |
201 | | pub response_done: bool, |
202 | | |
203 | | pub nfs_version: u16, |
204 | | |
205 | | /// is a special file tx that we look up by file_handle instead of XID |
206 | | pub is_file_tx: bool, |
207 | | pub is_file_closed: bool, |
208 | | pub file_handle: Vec<u8>, |
209 | | |
210 | | /// Procedure type specific data |
211 | | /// TODO see if this can be an `Option<Box<NFSTransactionTypeData>>`. Initial |
212 | | /// attempt failed. |
213 | | pub type_data: Option<NFSTransactionTypeData>, |
214 | | |
215 | | pub tx_data: AppLayerTxData, |
216 | | } |
217 | | |
218 | | impl Default for NFSTransaction { |
219 | 0 | fn default() -> Self { |
220 | 0 | Self::new() |
221 | 0 | } |
222 | | } |
223 | | |
224 | | impl NFSTransaction { |
225 | 327k | pub fn new() -> Self { |
226 | 327k | return Self { |
227 | 327k | id: 0, |
228 | 327k | xid: 0, |
229 | 327k | procedure: 0, |
230 | 327k | file_name:Vec::new(), |
231 | 327k | request_machine_name:Vec::new(), |
232 | 327k | request_uid:0, |
233 | 327k | request_gid:0, |
234 | 327k | rpc_response_status:0, |
235 | 327k | nfs_response_status:0, |
236 | 327k | auth_type: 0, |
237 | 327k | is_first: false, |
238 | 327k | is_last: false, |
239 | 327k | request_done: false, |
240 | 327k | response_done: false, |
241 | 327k | nfs_version:0, |
242 | 327k | is_file_tx: false, |
243 | 327k | is_file_closed: false, |
244 | 327k | file_handle:Vec::new(), |
245 | 327k | type_data: None, |
246 | 327k | tx_data: AppLayerTxData::new(), |
247 | 327k | } |
248 | 327k | } |
249 | | |
250 | 327k | pub fn free(&mut self) { |
251 | 327k | debug_validate_bug_on!(self.tx_data.files_opened > 1); |
252 | 327k | debug_validate_bug_on!(self.tx_data.files_logged > 1); |
253 | 327k | } |
254 | | } |
255 | | |
256 | | impl Transaction for NFSTransaction { |
257 | 1.09G | fn id(&self) -> u64 { |
258 | 1.09G | self.id |
259 | 1.09G | } |
260 | | } |
261 | | |
262 | | impl Drop for NFSTransaction { |
263 | 327k | fn drop(&mut self) { |
264 | 68.0k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = self.type_data { |
265 | 48.0k | if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { |
266 | 48.0k | tdf.file_tracker.file.free(sfcm); |
267 | 48.0k | } |
268 | 279k | } |
269 | 327k | self.free(); |
270 | 327k | } |
271 | | } |
272 | | |
273 | | #[derive(Debug)] |
274 | | pub struct NFSRequestXidMap { |
275 | | pub progver: u32, |
276 | | pub procedure: u32, |
277 | | pub chunk_offset: u64, |
278 | | pub file_name:Vec<u8>, |
279 | | |
280 | | /// READ replies can use this to get to the handle the request used |
281 | | pub file_handle:Vec<u8>, |
282 | | |
283 | | pub gssapi_proc: u32, |
284 | | pub gssapi_service: u32, |
285 | | } |
286 | | |
287 | | impl NFSRequestXidMap { |
288 | 733k | pub fn new(progver: u32, procedure: u32, chunk_offset: u64) -> NFSRequestXidMap { |
289 | 733k | NFSRequestXidMap { |
290 | 733k | progver, |
291 | 733k | procedure, |
292 | 733k | chunk_offset, |
293 | 733k | file_name:Vec::new(), |
294 | 733k | file_handle:Vec::new(), |
295 | 733k | gssapi_proc: 0, |
296 | 733k | gssapi_service: 0, |
297 | 733k | } |
298 | 733k | } |
299 | | } |
300 | | |
301 | | /// little wrapper around the FileTransferTracker::new_chunk method |
302 | 166k | pub fn filetracker_newchunk(ft: &mut FileTransferTracker, name: &[u8], data: &[u8], |
303 | 166k | chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32) |
304 | | { |
305 | 166k | if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { |
306 | 166k | ft.new_chunk(sfcm, name, data, chunk_offset, |
307 | 166k | chunk_size, fill_bytes, is_last, xid); |
308 | 166k | } |
309 | 166k | } |
310 | | |
311 | 0 | fn filetracker_trunc(ft: &mut FileTransferTracker) |
312 | | { |
313 | 0 | if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { |
314 | 0 | ft.trunc(sfcm); |
315 | 0 | } |
316 | 0 | } |
317 | | |
318 | 3.07k | pub fn filetracker_close(ft: &mut FileTransferTracker) |
319 | | { |
320 | 3.07k | if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { |
321 | 3.07k | ft.close(sfcm); |
322 | 3.07k | } |
323 | 3.07k | } |
324 | | |
325 | 116k | fn filetracker_update(ft: &mut FileTransferTracker, data: &[u8], gap_size: u32) -> u32 |
326 | | { |
327 | 116k | if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { |
328 | 116k | ft.update(sfcm, data, gap_size) |
329 | | } else { |
330 | 0 | 0 |
331 | | } |
332 | 116k | } |
333 | | |
334 | | |
335 | | #[derive(Debug)] |
336 | | pub struct NFSState { |
337 | | state_data: AppLayerStateData, |
338 | | |
339 | | /// map xid to procedure so replies can lookup the procedure |
340 | | pub requestmap: HashMap<u32, NFSRequestXidMap>, |
341 | | |
342 | | /// map file handle (1) to name (2) |
343 | | pub namemap: HashMap<Vec<u8>, Vec<u8>>, |
344 | | |
345 | | /// transactions list |
346 | | pub transactions: Vec<NFSTransaction>, |
347 | | |
348 | | /// partial record tracking |
349 | | pub ts_chunk_xid: u32, |
350 | | pub tc_chunk_xid: u32, |
351 | | /// size of the current chunk that we still need to receive |
352 | | pub ts_chunk_left: u32, |
353 | | pub tc_chunk_left: u32, |
354 | | /// file handle of in progress toserver WRITE file chunk |
355 | | ts_chunk_fh: Vec<u8>, |
356 | | |
357 | | ts_ssn_gap: bool, |
358 | | tc_ssn_gap: bool, |
359 | | |
360 | | ts_gap: bool, // last TS update was gap |
361 | | tc_gap: bool, // last TC update was gap |
362 | | |
363 | | is_udp: bool, |
364 | | |
365 | | /// true as long as we have file txs that are in a post-gap |
366 | | /// state. It means we'll do extra house keeping for those. |
367 | | check_post_gap_file_txs: bool, |
368 | | post_gap_files_checked: bool, |
369 | | |
370 | | pub nfs_version: u16, |
371 | | |
372 | | /// tx counter for assigning incrementing id's to tx's |
373 | | tx_id: u64, |
374 | | |
375 | | /// Timestamp in seconds of last update. This is packet time, |
376 | | /// potentially coming from pcaps. |
377 | | ts: u64, |
378 | | } |
379 | | |
380 | | impl Default for NFSState { |
381 | 0 | fn default() -> Self { |
382 | 0 | Self::new() |
383 | 0 | } |
384 | | } |
385 | | |
386 | | impl State<NFSTransaction> for NFSState { |
387 | 367M | fn get_transaction_count(&self) -> usize { |
388 | 367M | self.transactions.len() |
389 | 367M | } |
390 | | |
391 | 731M | fn get_transaction_by_index(&self, index: usize) -> Option<&NFSTransaction> { |
392 | 731M | self.transactions.get(index) |
393 | 731M | } |
394 | | } |
395 | | |
396 | | impl NFSState { |
397 | | /// Allocation function for a new TLS parser instance |
398 | 22.3k | pub fn new() -> NFSState { |
399 | 22.3k | NFSState { |
400 | 22.3k | state_data: AppLayerStateData::new(), |
401 | 22.3k | requestmap:HashMap::new(), |
402 | 22.3k | namemap:HashMap::new(), |
403 | 22.3k | transactions: Vec::new(), |
404 | 22.3k | ts_chunk_xid:0, |
405 | 22.3k | tc_chunk_xid:0, |
406 | 22.3k | ts_chunk_left:0, |
407 | 22.3k | tc_chunk_left:0, |
408 | 22.3k | ts_chunk_fh:Vec::new(), |
409 | 22.3k | ts_ssn_gap:false, |
410 | 22.3k | tc_ssn_gap:false, |
411 | 22.3k | ts_gap:false, |
412 | 22.3k | tc_gap:false, |
413 | 22.3k | is_udp:false, |
414 | 22.3k | check_post_gap_file_txs:false, |
415 | 22.3k | post_gap_files_checked:false, |
416 | 22.3k | nfs_version:0, |
417 | 22.3k | tx_id:0, |
418 | 22.3k | ts: 0, |
419 | 22.3k | } |
420 | 22.3k | } |
421 | | |
422 | 1.24M | fn update_ts(&mut self, ts: u64) { |
423 | 1.24M | if ts != self.ts { |
424 | 443 | self.ts = ts; |
425 | 443 | self.post_gap_files_checked = false; |
426 | 1.24M | } |
427 | 1.24M | } |
428 | | |
429 | 327k | pub fn new_tx(&mut self) -> NFSTransaction { |
430 | 327k | let mut tx = NFSTransaction::new(); |
431 | 327k | self.tx_id += 1; |
432 | 327k | tx.id = self.tx_id; |
433 | 327k | if self.transactions.len() > unsafe { NFS_MAX_TX } { |
434 | | // set at least one another transaction to the drop state |
435 | 71.1k | for tx_old in &mut self.transactions { |
436 | 71.1k | if !tx_old.request_done || !tx_old.response_done { |
437 | 70.9k | tx_old.tx_data.updated_tc = true; |
438 | 70.9k | tx_old.tx_data.updated_ts = true; |
439 | 70.9k | tx_old.request_done = true; |
440 | 70.9k | tx_old.response_done = true; |
441 | 70.9k | tx_old.is_file_closed = true; |
442 | 70.9k | tx_old.tx_data.set_event(NFSEvent::TooManyTransactions as u8); |
443 | 70.9k | break; |
444 | 201 | } |
445 | | } |
446 | 256k | } |
447 | 327k | return tx; |
448 | 327k | } |
449 | | |
450 | 141k | pub fn free_tx(&mut self, tx_id: u64) { |
451 | | //SCLogNotice!("Freeing TX with ID {}", tx_id); |
452 | 141k | let len = self.transactions.len(); |
453 | 141k | let mut found = false; |
454 | 141k | let mut index = 0; |
455 | 2.06M | for i in 0..len { |
456 | 2.06M | let tx = &self.transactions[i]; |
457 | 2.06M | if tx.id == tx_id + 1 { |
458 | 141k | found = true; |
459 | 141k | index = i; |
460 | 141k | break; |
461 | 1.92M | } |
462 | | } |
463 | 141k | if found { |
464 | 141k | SCLogDebug!("freeing TX with ID {} at index {}", tx_id, index); |
465 | 141k | self.transactions.remove(index); |
466 | 141k | } |
467 | 141k | } |
468 | | |
469 | 16.2k | pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NFSTransaction> { |
470 | 62.5k | return self.transactions.iter().find(|&tx| tx.id == tx_id + 1); |
471 | 16.2k | } |
472 | | |
473 | 84.4k | pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFSTransaction> { |
474 | 2.26M | return self.transactions.iter_mut().find(|tx| !tx.is_file_tx && tx.xid == tx_xid); |
475 | 84.4k | } |
476 | | |
477 | | /// Set an event. The event is set on the most recent transaction. |
478 | 624k | pub fn set_event(&mut self, event: NFSEvent) { |
479 | 624k | let len = self.transactions.len(); |
480 | 624k | if len == 0 { |
481 | 289k | return; |
482 | 334k | } |
483 | | |
484 | 334k | let tx = &mut self.transactions[len - 1]; |
485 | 334k | tx.tx_data.set_event(event as u8); |
486 | 624k | } |
487 | | |
488 | | // TODO maybe not enough users to justify a func |
489 | 84.4k | pub fn mark_response_tx_done(&mut self, xid: u32, rpc_status: u32, nfs_status: u32, resp_handle: &[u8]) |
490 | | { |
491 | 84.4k | if let Some(mytx) = self.get_tx_by_xid(xid) { |
492 | 66.6k | mytx.tx_data.updated_tc = true; |
493 | 66.6k | mytx.tx_data.updated_ts = true; |
494 | 66.6k | mytx.response_done = true; |
495 | 66.6k | mytx.rpc_response_status = rpc_status; |
496 | 66.6k | mytx.nfs_response_status = nfs_status; |
497 | 66.6k | if mytx.file_handle.is_empty() && !resp_handle.is_empty() { |
498 | 7.14k | mytx.file_handle = resp_handle.to_vec(); |
499 | 59.5k | } |
500 | | |
501 | | SCLogDebug!("process_reply_record: tx ID {} XID {:04X} REQUEST {} RESPONSE {}", |
502 | | mytx.id, mytx.xid, mytx.request_done, mytx.response_done); |
503 | 17.7k | } else { |
504 | 17.7k | //SCLogNotice!("process_reply_record: not TX found for XID {}", r.hdr.xid); |
505 | 17.7k | } |
506 | 84.4k | } |
507 | | |
508 | 988k | fn add_rpc_udp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { |
509 | 988k | let rpc_udp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); |
510 | | SCLogDebug!("rpc_udp_pdu ts frame {:?}", rpc_udp_ts_pdu); |
511 | 988k | rpc_udp_ts_pdu |
512 | 988k | } |
513 | | |
514 | 470k | fn add_rpc_udp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) { |
515 | 470k | let _rpc_udp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8); |
516 | | SCLogDebug!("rpc_creds ts frame {:?}", _rpc_udp_ts_creds); |
517 | 470k | } |
518 | | |
519 | 900k | fn add_rpc_tcp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { |
520 | 900k | let rpc_tcp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); |
521 | | SCLogDebug!("rpc_tcp_pdu ts frame {:?}", rpc_tcp_ts_pdu); |
522 | 900k | rpc_tcp_ts_pdu |
523 | 900k | } |
524 | | |
525 | 241k | fn add_rpc_tcp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) { |
526 | 241k | let _rpc_tcp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8); |
527 | | SCLogDebug!("rpc_tcp_ts_creds {:?}", _rpc_tcp_ts_creds); |
528 | 241k | } |
529 | | |
530 | 440k | fn add_nfs_ts_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) { |
531 | 440k | let _nfs_req_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8); |
532 | | SCLogDebug!("nfs_ts_pdu Frame {:?}", _nfs_req_pdu); |
533 | 440k | } |
534 | | |
535 | 221k | fn add_nfs4_ts_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) { |
536 | 221k | let _nfs4_ts_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8); |
537 | | SCLogDebug!("nfs4_ts_pdu Frame: {:?}", _nfs4_ts_pdu); |
538 | 221k | if nfs4_len > 8 { |
539 | 202k | let _nfs4_ts_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8); |
540 | 202k | SCLogDebug!("nfs4_ts_hdr Frame {:?}", _nfs4_ts_hdr); |
541 | 202k | let _nfs4_ts_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8); |
542 | 202k | SCLogDebug!("nfs4_ts_ops Frame {:?}", _nfs4_ts_ops); |
543 | 202k | } |
544 | 221k | } |
545 | | |
546 | 813k | fn add_rpc_udp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { |
547 | 813k | let rpc_udp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); |
548 | | SCLogDebug!("rpc_tc_pdu frame {:?}", rpc_udp_tc_pdu); |
549 | 813k | rpc_udp_tc_pdu |
550 | 813k | } |
551 | | |
552 | 129k | fn add_rpc_udp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) { |
553 | 129k | if rpc_len > 8 { |
554 | 129k | let _rpc_udp_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::RPCHdr as u8); |
555 | 129k | let _rpc_udp_tc_data = Frame::new(flow, stream_slice, &input[8..], rpc_len - 8, NFSFrameType::RPCData as u8); |
556 | 129k | SCLogDebug!("rpc_udp_tc_hdr frame {:?}", _rpc_udp_tc_hdr); |
557 | 129k | SCLogDebug!("rpc_udp_tc_data frame {:?}", _rpc_udp_tc_data); |
558 | 129k | } |
559 | 129k | } |
560 | | |
561 | 666k | fn add_rpc_tcp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) -> Option<Frame> { |
562 | 666k | let rpc_tcp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_tcp_len, NFSFrameType::RPCPdu as u8); |
563 | | SCLogDebug!("rpc_tcp_pdu tc frame {:?}", rpc_tcp_tc_pdu); |
564 | 666k | rpc_tcp_tc_pdu |
565 | 666k | } |
566 | | |
567 | 85.8k | fn add_rpc_tcp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) { |
568 | 85.8k | if rpc_tcp_len > 12 { |
569 | 85.8k | let _rpc_tcp_tc_hdr = Frame::new(flow, stream_slice, input, 12, NFSFrameType::RPCHdr as u8); |
570 | 85.8k | let _rpc_tcp_tc_data = Frame::new(flow, stream_slice, &input[12..], rpc_tcp_len - 12, NFSFrameType::RPCData as u8); |
571 | 85.8k | SCLogDebug!("rpc_tcp_tc_hdr frame {:?}", _rpc_tcp_tc_hdr); |
572 | 85.8k | SCLogDebug!("rpc_tcp_tc_data frame {:?}", _rpc_tcp_tc_data); |
573 | 85.8k | } |
574 | 85.8k | } |
575 | | |
576 | 94.4k | fn add_nfs_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) { |
577 | 94.4k | if nfs_len > 0 { |
578 | 84.8k | let _nfs_tc_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8); |
579 | 84.8k | SCLogDebug!("nfs_tc_pdu frame {:?}", _nfs_tc_pdu); |
580 | 84.8k | let _nfs_res_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFSStatus as u8); |
581 | 84.8k | SCLogDebug!("nfs_tc_status frame {:?}", _nfs_res_status); |
582 | 84.8k | } |
583 | 94.4k | } |
584 | | |
585 | 72.9k | fn add_nfs4_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) { |
586 | 72.9k | if nfs4_len > 0 { |
587 | 72.5k | let _nfs4_tc_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8); |
588 | 72.5k | SCLogDebug!("nfs4_tc_pdu frame {:?}", _nfs4_tc_pdu); |
589 | 72.5k | let _nfs4_tc_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFS4Status as u8); |
590 | 72.5k | SCLogDebug!("nfs4_tc_status frame {:?}", _nfs4_tc_status); |
591 | 72.5k | } |
592 | 72.9k | if nfs4_len > 8 { |
593 | 71.5k | let _nfs4_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8); |
594 | 71.5k | SCLogDebug!("nfs4_tc_hdr frame {:?}", _nfs4_tc_hdr); |
595 | 71.5k | let _nfs4_tc_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8); |
596 | 71.5k | SCLogDebug!("nfs4_tc_ops frame {:?}", _nfs4_tc_ops); |
597 | 71.5k | } |
598 | 72.9k | } |
599 | | |
600 | 0 | fn post_gap_housekeeping_for_files(&mut self) |
601 | | { |
602 | 0 | let mut post_gap_txs = false; |
603 | 0 | for tx in &mut self.transactions { |
604 | 0 | if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { |
605 | 0 | if f.post_gap_ts > 0 { |
606 | 0 | if self.ts > f.post_gap_ts { |
607 | 0 | tx.request_done = true; |
608 | 0 | tx.response_done = true; |
609 | 0 | filetracker_trunc(&mut f.file_tracker); |
610 | 0 | } else { |
611 | 0 | post_gap_txs = true; |
612 | 0 | } |
613 | 0 | } |
614 | 0 | } |
615 | | } |
616 | 0 | self.check_post_gap_file_txs = post_gap_txs; |
617 | 0 | } |
618 | | |
619 | | /* after a gap we will consider all transactions complete for our |
620 | | * direction. File transfer transactions are an exception. Those |
621 | | * can handle gaps. For the file transactions we set the current |
622 | | * (flow) time and prune them in 60 seconds if no update for them |
623 | | * was received. */ |
624 | 65.8k | fn post_gap_housekeeping(&mut self, dir: Direction) |
625 | | { |
626 | 65.8k | if self.ts_ssn_gap && dir == Direction::ToServer { |
627 | 166 | for tx in &mut self.transactions { |
628 | 22 | if tx.id >= self.tx_id { |
629 | | SCLogDebug!("post_gap_housekeeping: done"); |
630 | 22 | break; |
631 | 0 | } |
632 | 0 | if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { |
633 | | // leaving FILE txs open as they can deal with gaps. We |
634 | | // remove them after 60 seconds of no activity though. |
635 | 0 | if f.post_gap_ts == 0 { |
636 | 0 | f.post_gap_ts = self.ts + 60; |
637 | 0 | self.check_post_gap_file_txs = true; |
638 | 0 | } |
639 | 0 | } else { |
640 | 0 | SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id); |
641 | 0 | tx.request_done = true; |
642 | 0 | } |
643 | | } |
644 | 65.6k | } else if self.tc_ssn_gap && dir == Direction::ToClient { |
645 | 198 | for tx in &mut self.transactions { |
646 | 13 | if tx.id >= self.tx_id { |
647 | | SCLogDebug!("post_gap_housekeeping: done"); |
648 | 13 | break; |
649 | 0 | } |
650 | 0 | if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { |
651 | | // leaving FILE txs open as they can deal with gaps. We |
652 | | // remove them after 60 seconds of no activity though. |
653 | 0 | if f.post_gap_ts == 0 { |
654 | 0 | f.post_gap_ts = self.ts + 60; |
655 | 0 | self.check_post_gap_file_txs = true; |
656 | 0 | } |
657 | 0 | } else { |
658 | 0 | SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id); |
659 | 0 | tx.request_done = true; |
660 | 0 | tx.response_done = true; |
661 | 0 | } |
662 | | } |
663 | 65.4k | } |
664 | 65.8k | } |
665 | | |
666 | 18.3k | pub fn process_request_record_lookup(&mut self, r: &RpcPacket, xidmap: &mut NFSRequestXidMap) { |
667 | 18.3k | match parse_nfs3_request_lookup(r.prog_data) { |
668 | 6.39k | Ok((_, lookup)) => { |
669 | 6.39k | SCLogDebug!("LOOKUP {:?}", lookup); |
670 | 6.39k | xidmap.file_name = lookup.name_vec; |
671 | 6.39k | }, |
672 | 11.9k | _ => { |
673 | 11.9k | self.set_event(NFSEvent::MalformedData); |
674 | 11.9k | }, |
675 | | }; |
676 | 18.3k | } |
677 | | |
678 | 38.8k | pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) { |
679 | 38.8k | if let Some(n) = self.namemap.get(&xidmap.file_handle) { |
680 | 3.72k | SCLogDebug!("xidmap_handle2name: name {:?}", n); |
681 | 3.72k | xidmap.file_name = n.to_vec(); |
682 | 35.1k | } else { |
683 | 35.1k | SCLogDebug!("xidmap_handle2name: object {:?} not found", xidmap.file_handle); |
684 | 35.1k | } |
685 | 38.8k | } |
686 | | |
687 | | /// complete request record |
688 | 642k | fn process_request_record(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcPacket) { |
689 | | SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}", |
690 | | r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); |
691 | | |
692 | 642k | match r.progver { |
693 | | 4 => { |
694 | 221k | self.add_nfs4_ts_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
695 | 221k | self.process_request_record_v4(r) |
696 | | }, |
697 | | 3 => { |
698 | 402k | self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
699 | 402k | self.process_request_record_v3(r) |
700 | | }, |
701 | | 2 => { |
702 | 603 | self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
703 | 603 | self.process_request_record_v2(r) |
704 | | }, |
705 | 17.6k | _ => { }, |
706 | | } |
707 | 642k | } |
708 | | |
709 | 48.0k | pub fn new_file_tx(&mut self, file_handle: &[u8], file_name: &[u8], direction: Direction) |
710 | 48.0k | -> &mut NFSTransaction |
711 | | { |
712 | 48.0k | let mut tx = self.new_tx(); |
713 | 48.0k | tx.file_name = file_name.to_vec(); |
714 | 48.0k | tx.file_handle = file_handle.to_vec(); |
715 | 48.0k | tx.is_file_tx = true; |
716 | | |
717 | 48.0k | tx.type_data = Some(NFSTransactionTypeData::FILE(NFSTransactionFile::new())); |
718 | 48.0k | if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data { |
719 | 48.0k | d.direction = direction; |
720 | 48.0k | d.file_tracker.tx_id = tx.id - 1; |
721 | 48.0k | tx.tx_data.update_file_flags(self.state_data.file_flags); |
722 | 48.0k | d.update_file_flags(tx.tx_data.file_flags); |
723 | 48.0k | } |
724 | 48.0k | tx.tx_data.init_files_opened(); |
725 | 48.0k | tx.tx_data.file_tx = if direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; // TODO direction to flag func? |
726 | | SCLogDebug!("new_file_tx: TX FILE created: ID {} NAME {}", |
727 | | tx.id, String::from_utf8_lossy(file_name)); |
728 | 48.0k | self.transactions.push(tx); |
729 | 48.0k | let tx_ref = self.transactions.last_mut(); |
730 | 48.0k | return tx_ref.unwrap(); |
731 | 48.0k | } |
732 | | |
733 | 384k | pub fn get_file_tx_by_handle(&mut self, file_handle: &[u8], direction: Direction) |
734 | 384k | -> Option<&mut NFSTransaction> |
735 | | { |
736 | 384k | let fh = file_handle.to_vec(); |
737 | 42.6M | for tx in &mut self.transactions { |
738 | 33.8M | if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data { |
739 | 33.5M | if tx.is_file_tx && !tx.is_file_closed && |
740 | 577k | direction == d.direction && |
741 | 523k | tx.file_handle == fh |
742 | | { |
743 | 270k | tx.tx_data.update_file_flags(self.state_data.file_flags); |
744 | 270k | d.update_file_flags(tx.tx_data.file_flags); |
745 | | SCLogDebug!("Found NFS file TX with ID {} XID {:04X}", tx.id, tx.xid); |
746 | 270k | tx.tx_data.updated_tc = true; |
747 | 270k | tx.tx_data.updated_ts = true; |
748 | 270k | return Some(tx); |
749 | 33.2M | } |
750 | 8.99M | } |
751 | | } |
752 | | SCLogDebug!("Failed to find NFS TX with handle {:?}", file_handle); |
753 | 114k | return None; |
754 | 384k | } |
755 | | |
756 | 159k | pub fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { |
757 | 159k | let mut fill_bytes = 0; |
758 | 159k | let pad = w.count % 4; |
759 | 159k | if pad != 0 { |
760 | 12.7k | fill_bytes = 4 - pad; |
761 | 147k | } |
762 | | |
763 | | // linux defines a max of 1mb. Allow several multiples. |
764 | 159k | if w.count == 0 || w.count > 16777216 { |
765 | 6.66k | return 0; |
766 | 153k | } |
767 | | |
768 | | // for now assume that stable FILE_SYNC flags means a single chunk |
769 | 153k | let is_last = w.stable == 2; |
770 | 153k | let file_handle = w.handle.value.to_vec(); |
771 | 153k | let file_name = if let Some(name) = self.namemap.get(w.handle.value) { |
772 | | SCLogDebug!("WRITE name {:?}", name); |
773 | 3.42k | name.to_vec() |
774 | | } else { |
775 | | SCLogDebug!("WRITE object {:?} not found", w.handle.value); |
776 | 149k | Vec::new() |
777 | | }; |
778 | | |
779 | 153k | let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) { |
780 | 114k | Some(tx) => { |
781 | 114k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
782 | 114k | filetracker_newchunk(&mut tdf.file_tracker, |
783 | 114k | &file_name, w.file_data, w.offset, |
784 | 114k | w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); |
785 | 114k | tdf.chunk_count += 1; |
786 | 114k | if is_last { |
787 | 9.22k | tdf.file_last_xid = r.hdr.xid; |
788 | 9.22k | tx.is_last = true; |
789 | 9.22k | tx.response_done = true; |
790 | 9.22k | tx.is_file_closed = true; |
791 | 104k | } |
792 | 114k | true |
793 | | } else { |
794 | 0 | false |
795 | | } |
796 | | }, |
797 | 38.9k | None => { false }, |
798 | | }; |
799 | 153k | if !found { |
800 | 38.9k | let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer); |
801 | 38.9k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
802 | 38.9k | filetracker_newchunk(&mut tdf.file_tracker, |
803 | 38.9k | &file_name, w.file_data, w.offset, |
804 | 38.9k | w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); |
805 | 38.9k | tx.procedure = NFSPROC3_WRITE; |
806 | 38.9k | tx.xid = r.hdr.xid; |
807 | 38.9k | tx.is_first = true; |
808 | 38.9k | tx.nfs_version = r.progver as u16; |
809 | 38.9k | if is_last { |
810 | 24.3k | tdf.file_last_xid = r.hdr.xid; |
811 | 24.3k | tx.is_last = true; |
812 | 24.3k | tx.request_done = true; |
813 | 24.3k | tx.is_file_closed = true; |
814 | 24.3k | } |
815 | 0 | } |
816 | 114k | } |
817 | 153k | if !self.is_udp { |
818 | 67.1k | self.ts_chunk_xid = r.hdr.xid; |
819 | 67.1k | debug_validate_bug_on!(w.file_data.len() as u32 > w.count); |
820 | 67.1k | self.ts_chunk_left = w.count - w.file_data.len() as u32; |
821 | 67.1k | self.ts_chunk_fh = file_handle; |
822 | | SCLogDebug!("REQUEST chunk_xid {:04X} chunk_left {}", self.ts_chunk_xid, self.ts_chunk_left); |
823 | 86.0k | } |
824 | 153k | 0 |
825 | 159k | } |
826 | | |
827 | 71.4k | fn process_partial_write_request_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { |
828 | | SCLogDebug!("REQUEST {} procedure {} blob size {}", r.hdr.xid, r.procedure, r.prog_data.len()); |
829 | | |
830 | 71.4k | let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); |
831 | 71.4k | xidmap.file_handle = w.handle.value.to_vec(); |
832 | 71.4k | if self.requestmap.len() < unsafe { NFS_CFG_MAX_REQ } { |
833 | 71.4k | self.requestmap.insert(r.hdr.xid, xidmap); |
834 | 71.4k | } |
835 | | |
836 | 71.4k | return self.process_write_record(r, w); |
837 | 71.4k | } |
838 | | |
839 | 215k | fn process_reply_record(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcReplyPacket) -> u32 { |
840 | | let mut xidmap; |
841 | 215k | match self.requestmap.remove(&r.hdr.xid) { |
842 | 169k | Some(p) => { xidmap = p; }, |
843 | | _ => { |
844 | | SCLogDebug!("REPLY: xid {:04X} NOT FOUND. GAPS? TS:{} TC:{}", |
845 | | r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap); |
846 | | |
847 | | // TODO we might be able to try to infer from the size + data |
848 | | // that this is a READ reply and pass the data to the file API anyway? |
849 | 45.6k | return 0; |
850 | | }, |
851 | | } |
852 | | SCLogDebug!("process_reply_record: removed xid {:04X} from requestmap", |
853 | | r.hdr.xid); |
854 | | |
855 | 169k | if self.nfs_version == 0 { |
856 | 5.45k | self.nfs_version = xidmap.progver as u16; |
857 | 163k | } |
858 | | |
859 | 169k | match xidmap.progver { |
860 | | 2 => { |
861 | | SCLogDebug!("NFSv2 reply record"); |
862 | 17.4k | self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
863 | 17.4k | self.process_reply_record_v2(r, &xidmap); |
864 | 17.4k | return 0; |
865 | | }, |
866 | | 3 => { |
867 | | SCLogDebug!("NFSv3 reply record"); |
868 | 76.9k | self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
869 | 76.9k | self.process_reply_record_v3(r, &mut xidmap); |
870 | 76.9k | return 0; |
871 | | }, |
872 | | 4 => { |
873 | | SCLogDebug!("NFSv4 reply record"); |
874 | 72.9k | self.add_nfs4_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); |
875 | 72.9k | self.process_reply_record_v4(r, &mut xidmap); |
876 | 72.9k | return 0; |
877 | | }, |
878 | | _ => { |
879 | | SCLogDebug!("Invalid NFS version"); |
880 | 2.08k | self.set_event(NFSEvent::NonExistingVersion); |
881 | 2.08k | return 0; |
882 | | }, |
883 | | } |
884 | 215k | } |
885 | | |
886 | | // update in progress chunks for file transfers |
887 | | // return how much data we consumed |
888 | 1.24M | fn filetracker_update(&mut self, direction: Direction, data: &[u8], gap_size: u32) -> u32 { |
889 | 1.24M | let mut chunk_left = if direction == Direction::ToServer { |
890 | 651k | self.ts_chunk_left |
891 | | } else { |
892 | 598k | self.tc_chunk_left |
893 | | }; |
894 | 1.24M | if chunk_left == 0 { |
895 | 1.06M | return 0 |
896 | 181k | } |
897 | 181k | let xid = if direction == Direction::ToServer { |
898 | 139k | self.ts_chunk_xid |
899 | | } else { |
900 | 42.0k | self.tc_chunk_xid |
901 | | }; |
902 | | SCLogDebug!("filetracker_update: chunk left {}, input {} chunk_xid {:04X}", chunk_left, data.len(), xid); |
903 | | |
904 | | let file_handle; |
905 | | // we have the data that we expect |
906 | 181k | if chunk_left <= data.len() as u32 { |
907 | 31.5k | chunk_left = 0; |
908 | | |
909 | 31.5k | if direction == Direction::ToServer { |
910 | 30.5k | self.ts_chunk_xid = 0; |
911 | 30.5k | file_handle = self.ts_chunk_fh.to_vec(); |
912 | 30.5k | self.ts_chunk_fh.clear(); |
913 | 30.5k | } else { |
914 | 983 | self.tc_chunk_xid = 0; |
915 | | |
916 | | // chunk done, remove requestmap entry |
917 | 983 | match self.requestmap.remove(&xid) { |
918 | | None => { |
919 | | SCLogDebug!("no file handle found for XID {:04X}", xid); |
920 | 195 | return 0 |
921 | | }, |
922 | 788 | Some(xidmap) => { |
923 | 788 | file_handle = xidmap.file_handle.to_vec(); |
924 | 788 | }, |
925 | | } |
926 | | } |
927 | | } else { |
928 | 149k | chunk_left -= data.len() as u32; |
929 | | |
930 | 149k | if direction == Direction::ToServer { |
931 | 108k | file_handle = self.ts_chunk_fh.to_vec(); |
932 | 108k | } else { |
933 | | // see if we have a file handle to work on |
934 | 41.0k | match self.requestmap.get(&xid) { |
935 | | None => { |
936 | | SCLogDebug!("no file handle found for XID {:04X}", xid); |
937 | 687 | return 0 |
938 | | }, |
939 | 40.3k | Some(xidmap) => { |
940 | 40.3k | file_handle = xidmap.file_handle.to_vec(); |
941 | 40.3k | }, |
942 | | } |
943 | | } |
944 | | } |
945 | | |
946 | 180k | if direction == Direction::ToServer { |
947 | 139k | self.ts_chunk_left = chunk_left; |
948 | 139k | } else { |
949 | 41.1k | self.tc_chunk_left = chunk_left; |
950 | 41.1k | } |
951 | | |
952 | 180k | let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap; |
953 | | // get the tx and update it |
954 | 180k | let consumed = match self.get_file_tx_by_handle(&file_handle, direction) { |
955 | 116k | Some(tx) => { |
956 | 116k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
957 | 116k | if ssn_gap { |
958 | 0 | let queued_data = tdf.file_tracker.get_queued_size(); |
959 | 0 | if queued_data > 2000000 { // TODO should probably be configurable |
960 | 0 | SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data); |
961 | 0 | filetracker_trunc(&mut tdf.file_tracker); |
962 | 0 | } |
963 | 116k | } |
964 | | |
965 | | // reset timestamp if we get called after a gap |
966 | 116k | if tdf.post_gap_ts > 0 { |
967 | 0 | tdf.post_gap_ts = 0; |
968 | 116k | } |
969 | | |
970 | 116k | tdf.chunk_count += 1; |
971 | 116k | let cs = filetracker_update(&mut tdf.file_tracker, data, gap_size); |
972 | | /* see if we need to close the tx */ |
973 | 116k | if tdf.file_tracker.is_done() { |
974 | 1.59k | if direction == Direction::ToClient { |
975 | 961 | tx.response_done = true; |
976 | 961 | tx.is_file_closed = true; |
977 | 961 | SCLogDebug!("TX {} response is done now that the file track is ready", tx.id); |
978 | 961 | } else { |
979 | 637 | tx.request_done = true; |
980 | 637 | tx.is_file_closed = true; |
981 | 637 | SCLogDebug!("TX {} request is done now that the file track is ready", tx.id); |
982 | 637 | } |
983 | 115k | } |
984 | 116k | cs |
985 | | } else { |
986 | 0 | 0 |
987 | | } |
988 | | }, |
989 | 63.3k | None => { 0 }, |
990 | | }; |
991 | 180k | return consumed; |
992 | 1.24M | } |
993 | | |
994 | | /// xidmapr is an Option as it's already removed from the map if we |
995 | | /// have a complete record. Otherwise we do a lookup ourselves. |
996 | 10.1k | pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>, |
997 | 10.1k | reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32 |
998 | | { |
999 | | let file_name; |
1000 | | let file_handle; |
1001 | | let chunk_offset; |
1002 | | let nfs_version; |
1003 | | |
1004 | 10.1k | let mut fill_bytes = 0; |
1005 | 10.1k | let pad = reply.count % 4; |
1006 | 10.1k | if pad != 0 { |
1007 | 7.26k | fill_bytes = 4 - pad; |
1008 | 7.26k | } |
1009 | | |
1010 | | // linux defines a max of 1mb. Allow several multiples. |
1011 | 10.1k | if reply.count == 0 || reply.count > 16777216 { |
1012 | 2.19k | return 0; |
1013 | 7.96k | } |
1014 | | |
1015 | 7.96k | match xidmapr { |
1016 | 5.87k | Some(xidmap) => { |
1017 | 5.87k | file_name = xidmap.file_name.to_vec(); |
1018 | 5.87k | file_handle = xidmap.file_handle.to_vec(); |
1019 | 5.87k | chunk_offset = xidmap.chunk_offset; |
1020 | 5.87k | nfs_version = xidmap.progver; |
1021 | 5.87k | }, |
1022 | | None => { |
1023 | 2.08k | if let Some(xidmap) = self.requestmap.get(&r.hdr.xid) { |
1024 | 2.08k | file_name = xidmap.file_name.to_vec(); |
1025 | 2.08k | file_handle = xidmap.file_handle.to_vec(); |
1026 | 2.08k | chunk_offset = xidmap.chunk_offset; |
1027 | 2.08k | nfs_version = xidmap.progver; |
1028 | 2.08k | } else { |
1029 | 0 | return 0; |
1030 | | } |
1031 | | }, |
1032 | | } |
1033 | | SCLogDebug!("chunk_offset {}", chunk_offset); |
1034 | | |
1035 | 7.96k | let mut is_last = reply.eof; |
1036 | | SCLogDebug!("XID {} is_last {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}", |
1037 | | r.hdr.xid, is_last, fill_bytes, reply.count, reply.data_len, reply.data.len()); |
1038 | | |
1039 | 7.96k | if nfs_version == 2 { |
1040 | 4.20k | let size = match parse_nfs2_attribs(reply.attr_blob) { |
1041 | 4.20k | Ok((_, ref attr)) => attr.asize, |
1042 | 0 | _ => 0, |
1043 | | }; |
1044 | | SCLogDebug!("NFSv2 READ reply record: File size {}. Offset {} data len {}: total {}", |
1045 | | size, chunk_offset, reply.data_len, chunk_offset + reply.data_len as u64); |
1046 | | |
1047 | 4.20k | if size as u64 == chunk_offset + reply.data_len as u64 { |
1048 | 1.54k | is_last = true; |
1049 | 2.66k | } |
1050 | | |
1051 | 3.75k | } |
1052 | | |
1053 | 7.96k | let is_partial = reply.data.len() < reply.count as usize; |
1054 | | SCLogDebug!("partial data? {}", is_partial); |
1055 | | |
1056 | 7.96k | let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToClient) { |
1057 | 5.55k | Some(tx) => { |
1058 | | SCLogDebug!("updated TX {:?}", tx); |
1059 | 5.55k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
1060 | 5.55k | filetracker_newchunk(&mut tdf.file_tracker, |
1061 | 5.55k | &file_name, reply.data, chunk_offset, |
1062 | 5.55k | reply.count, fill_bytes as u8, is_last, &r.hdr.xid); |
1063 | 5.55k | tdf.chunk_count += 1; |
1064 | 5.55k | if is_last { |
1065 | 2.45k | tdf.file_last_xid = r.hdr.xid; |
1066 | 2.45k | tx.rpc_response_status = r.reply_state; |
1067 | 2.45k | tx.nfs_response_status = reply.status; |
1068 | 2.45k | tx.is_last = true; |
1069 | 2.45k | tx.request_done = true; |
1070 | | |
1071 | | /* if this is a partial record we will close the tx |
1072 | | * when we've received the final data */ |
1073 | 2.45k | if !is_partial { |
1074 | 1.80k | tx.response_done = true; |
1075 | 1.80k | SCLogDebug!("TX {} is DONE", tx.id); |
1076 | 1.80k | } |
1077 | 3.10k | } |
1078 | 5.55k | true |
1079 | | } else { |
1080 | 0 | false |
1081 | | } |
1082 | | }, |
1083 | 2.40k | None => { false }, |
1084 | | }; |
1085 | 7.96k | if !found { |
1086 | 2.40k | let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToClient); |
1087 | 2.40k | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
1088 | 2.40k | filetracker_newchunk(&mut tdf.file_tracker, |
1089 | 2.40k | &file_name, reply.data, chunk_offset, |
1090 | 2.40k | reply.count, fill_bytes as u8, is_last, &r.hdr.xid); |
1091 | 2.40k | tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ }; |
1092 | 2.40k | tx.xid = r.hdr.xid; |
1093 | 2.40k | tx.is_first = true; |
1094 | 2.40k | if is_last { |
1095 | 1.97k | tdf.file_last_xid = r.hdr.xid; |
1096 | 1.97k | tx.rpc_response_status = r.reply_state; |
1097 | 1.97k | tx.nfs_response_status = reply.status; |
1098 | 1.97k | tx.is_last = true; |
1099 | 1.97k | tx.request_done = true; |
1100 | | |
1101 | | /* if this is a partial record we will close the tx |
1102 | | * when we've received the final data */ |
1103 | 1.97k | if !is_partial { |
1104 | 1.70k | tx.response_done = true; |
1105 | 1.70k | SCLogDebug!("TX {} is DONE", tx.id); |
1106 | 1.70k | } |
1107 | 432 | } |
1108 | 0 | } |
1109 | 5.55k | } |
1110 | | |
1111 | 7.96k | if !self.is_udp { |
1112 | 2.11k | self.tc_chunk_xid = r.hdr.xid; |
1113 | 2.11k | debug_validate_bug_on!(reply.data.len() as u32 > reply.count); |
1114 | 2.11k | self.tc_chunk_left = reply.count - reply.data.len() as u32; |
1115 | 5.85k | } |
1116 | | |
1117 | | SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {} chunk_xid {:04X}", |
1118 | | r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left, |
1119 | | self.tc_chunk_xid); |
1120 | 7.96k | 0 |
1121 | 10.1k | } |
1122 | | |
1123 | 2.84k | fn process_partial_read_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &NfsReplyRead<'b>) -> u32 { |
1124 | | SCLogDebug!("REPLY {} to procedure READ blob size {} / {}", |
1125 | | r.hdr.xid, r.prog_data.len(), reply.count); |
1126 | | |
1127 | 2.84k | return self.process_read_record(r, reply, None); |
1128 | 2.84k | } |
1129 | | |
1130 | 22.2k | fn peek_reply_record(&mut self, r: &RpcPacketHeader) -> u32 { |
1131 | 22.2k | if let Some(xidmap) = self.requestmap.get(&r.xid) { |
1132 | 7.39k | return xidmap.procedure; |
1133 | | } else { |
1134 | | SCLogDebug!("REPLY: xid {} NOT FOUND", r.xid); |
1135 | 14.8k | return 0; |
1136 | | } |
1137 | 22.2k | } |
1138 | | |
1139 | 134 | pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> AppLayerResult { |
1140 | | SCLogDebug!("parse_tcp_data_ts_gap ({})", gap_size); |
1141 | 134 | let gap = vec![0; gap_size as usize]; |
1142 | 134 | let consumed = self.filetracker_update(Direction::ToServer, &gap, gap_size); |
1143 | 134 | if consumed > gap_size { |
1144 | | SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); |
1145 | 0 | return AppLayerResult::ok(); |
1146 | 134 | } |
1147 | 134 | self.ts_ssn_gap = true; |
1148 | 134 | self.ts_gap = true; |
1149 | | SCLogDebug!("parse_tcp_data_ts_gap ({}) done", gap_size); |
1150 | 134 | return AppLayerResult::ok(); |
1151 | 134 | } |
1152 | | |
1153 | 150 | pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> AppLayerResult { |
1154 | | SCLogDebug!("parse_tcp_data_tc_gap ({})", gap_size); |
1155 | 150 | let gap = vec![0; gap_size as usize]; |
1156 | 150 | let consumed = self.filetracker_update(Direction::ToClient, &gap, gap_size); |
1157 | 150 | if consumed > gap_size { |
1158 | | SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); |
1159 | 0 | return AppLayerResult::ok(); |
1160 | 150 | } |
1161 | 150 | self.tc_ssn_gap = true; |
1162 | 150 | self.tc_gap = true; |
1163 | | SCLogDebug!("parse_tcp_data_tc_gap ({}) done", gap_size); |
1164 | 150 | return AppLayerResult::ok(); |
1165 | 150 | } |
1166 | | |
1167 | | /// Handle partial records |
1168 | 314k | fn parse_tcp_partial_data_ts<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8], |
1169 | 314k | phdr: &RpcRequestPacketPartial, rec_size: usize) -> AppLayerResult { |
1170 | | // special case: avoid buffering file write blobs |
1171 | | // as these can be large. |
1172 | 314k | if rec_size >= 512 && cur_i.len() >= 44 { |
1173 | | // large record, likely file xfer |
1174 | | SCLogDebug!("large record {}, likely file xfer", rec_size); |
1175 | | |
1176 | | // quick peek, are we in WRITE mode? |
1177 | 183k | if phdr.procedure == NFSPROC3_WRITE { |
1178 | | SCLogDebug!("CONFIRMED WRITE: large record {}, file chunk xfer", rec_size); |
1179 | | |
1180 | | // lets try to parse the RPC record. Might fail with Incomplete. |
1181 | 81.8k | match parse_rpc(cur_i, false) { |
1182 | 76.4k | Ok((_rem, ref hdr)) => { |
1183 | | // we got here because rec_size > input, so we should never have |
1184 | | // remaining data |
1185 | 76.4k | debug_validate_bug_on!(!_rem.is_empty()); |
1186 | | |
1187 | 76.4k | match parse_nfs3_request_write(hdr.prog_data, false) { |
1188 | 71.4k | Ok((_, ref w)) => { |
1189 | | // deal with the partial nfs write data |
1190 | 71.4k | self.process_partial_write_request_record(hdr, w); |
1191 | 71.4k | return AppLayerResult::ok(); |
1192 | | } |
1193 | 73 | Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { |
1194 | 73 | self.set_event(NFSEvent::MalformedData); |
1195 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1196 | 73 | return AppLayerResult::err(); |
1197 | | } |
1198 | 4.90k | Err(Err::Incomplete(_)) => { |
1199 | 4.90k | // this is normal, fall through to incomplete handling |
1200 | 4.90k | } |
1201 | | } |
1202 | | } |
1203 | 5.29k | Err(Err::Incomplete(_)) => { |
1204 | 5.29k | // size check was done for a minimal RPC record size, |
1205 | 5.29k | // so Incomplete is normal. |
1206 | 5.29k | SCLogDebug!("TS data incomplete"); |
1207 | 5.29k | } |
1208 | 58 | Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { |
1209 | 58 | self.set_event(NFSEvent::MalformedData); |
1210 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1211 | 58 | return AppLayerResult::err(); |
1212 | | } |
1213 | | } |
1214 | 101k | } |
1215 | 130k | } |
1216 | | // make sure we pass a value higher than current input |
1217 | | // but lower than the record size |
1218 | 242k | let n1 = cmp::max(cur_i.len(), 1024); |
1219 | 242k | let n2 = cmp::min(n1, rec_size); |
1220 | 242k | return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32); |
1221 | 314k | } |
1222 | | |
1223 | | /// Parsing function, handling TCP chunks fragmentation |
1224 | 651k | pub fn parse_tcp_data_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { |
1225 | 651k | let mut cur_i = stream_slice.as_slice(); |
1226 | | // take care of in progress file chunk transfers |
1227 | | // and skip buffer beyond it |
1228 | 651k | let consumed = self.filetracker_update(Direction::ToServer, cur_i, 0); |
1229 | 651k | if consumed > 0 { |
1230 | 13.8k | if consumed > cur_i.len() as u32 { |
1231 | 0 | return AppLayerResult::err(); |
1232 | 13.8k | } |
1233 | 13.8k | cur_i = &cur_i[consumed as usize..]; |
1234 | 637k | } |
1235 | 651k | if cur_i.is_empty() { |
1236 | 10.5k | return AppLayerResult::ok(); |
1237 | 640k | } |
1238 | 640k | if self.ts_gap { |
1239 | | SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len()); |
1240 | | |
1241 | 178 | let mut _cnt = 0; |
1242 | 3.80k | while !cur_i.is_empty() { |
1243 | 3.80k | _cnt += 1; |
1244 | 3.80k | match nfs_probe(cur_i, Direction::ToServer) { |
1245 | | 1 => { |
1246 | | SCLogDebug!("expected data found"); |
1247 | 78 | self.ts_gap = false; |
1248 | 78 | break; |
1249 | | }, |
1250 | | 0 => { |
1251 | | SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", |
1252 | | cur_i.len(), _cnt); |
1253 | 100 | return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32); |
1254 | | }, |
1255 | | -1 => { |
1256 | 3.62k | cur_i = &cur_i[1..]; |
1257 | 3.62k | if cur_i.is_empty() { |
1258 | 0 | SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt); |
1259 | 3.62k | } |
1260 | | }, |
1261 | | _ => { |
1262 | 0 | return AppLayerResult::err(); |
1263 | | }, |
1264 | | } |
1265 | | } |
1266 | | SCLogDebug!("TS GAP handling done (input {})", cur_i.len()); |
1267 | 640k | } |
1268 | | |
1269 | 946k | while !cur_i.is_empty() { // min record size |
1270 | 900k | self.add_rpc_tcp_ts_pdu(flow, stream_slice, cur_i, cur_i.len() as i64); |
1271 | 900k | match parse_rpc_request_partial(cur_i) { |
1272 | 620k | Ok((_, ref rpc_phdr)) => { |
1273 | 620k | let rec_size = (rpc_phdr.hdr.frag_len + 4) as usize; |
1274 | | |
1275 | | // Handle partial records |
1276 | 620k | if rec_size > cur_i.len() { |
1277 | 314k | return self.parse_tcp_partial_data_ts(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size); |
1278 | 305k | } |
1279 | | |
1280 | | // we have the full records size worth of data, |
1281 | | // let's parse it. Errors lead to event, but are |
1282 | | // not fatal as we already have enough info to |
1283 | | // go to the next record. |
1284 | 305k | match parse_rpc(cur_i, true) { |
1285 | 241k | Ok((_, ref rpc_record)) => { |
1286 | 241k | self.add_rpc_tcp_ts_creds(flow, stream_slice, &cur_i[RPC_TCP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64); |
1287 | 241k | self.process_request_record(flow, stream_slice, rpc_record); |
1288 | 241k | } |
1289 | 10.4k | Err(Err::Incomplete(_)) => { |
1290 | 10.4k | self.set_event(NFSEvent::MalformedData); |
1291 | 10.4k | } |
1292 | 53.7k | Err(Err::Error(_e)) | |
1293 | 53.7k | Err(Err::Failure(_e)) => { |
1294 | 53.7k | self.set_event(NFSEvent::MalformedData); |
1295 | 53.7k | SCLogDebug!("Parsing failed: {:?}", _e); |
1296 | 53.7k | } |
1297 | | } |
1298 | 305k | cur_i = &cur_i[rec_size..]; |
1299 | | } |
1300 | 278k | Err(Err::Incomplete(needed)) => { |
1301 | 278k | if let Needed::Size(n) = needed { |
1302 | | SCLogDebug!("Not enough data for partial RPC header {:?}", needed); |
1303 | | // 28 is the partial RPC header size parse_rpc_request_partial |
1304 | | // looks for. |
1305 | 278k | let n = usize::from(n); |
1306 | 278k | let need = if n > 28 { n } else { 28 }; |
1307 | 278k | return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32); |
1308 | 0 | } |
1309 | 0 | return AppLayerResult::err(); |
1310 | | } |
1311 | | /* This error is fatal. If we failed to parse the RPC hdr we don't |
1312 | | * have a length and we don't know where the next record starts. */ |
1313 | 2.04k | Err(Err::Error(_e)) | |
1314 | 0 | Err(Err::Failure(_e)) => { |
1315 | 2.04k | self.set_event(NFSEvent::MalformedData); |
1316 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1317 | 2.04k | return AppLayerResult::err(); |
1318 | | } |
1319 | | } |
1320 | | }; |
1321 | | |
1322 | 45.6k | self.post_gap_housekeeping(Direction::ToServer); |
1323 | 45.6k | if self.check_post_gap_file_txs && !self.post_gap_files_checked { |
1324 | 0 | self.post_gap_housekeeping_for_files(); |
1325 | 0 | self.post_gap_files_checked = true; |
1326 | 45.6k | } |
1327 | | |
1328 | 45.6k | AppLayerResult::ok() |
1329 | 651k | } |
1330 | | |
1331 | | /// Handle partial records |
1332 | 150k | fn parse_tcp_partial_data_tc<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8], |
1333 | 150k | phdr: &RpcPacketHeader, rec_size: usize) -> AppLayerResult { |
1334 | | // special case: avoid buffering file read blobs |
1335 | | // as these can be large. |
1336 | 150k | if rec_size >= 512 && cur_i.len() >= 128 {//36 { |
1337 | | // large record, likely file xfer |
1338 | | SCLogDebug!("large record {}, likely file xfer", rec_size); |
1339 | | |
1340 | | // quick peek, are in READ mode? |
1341 | 22.2k | if self.peek_reply_record(phdr) == NFSPROC3_READ { |
1342 | | SCLogDebug!("CONFIRMED large READ record {}, likely file chunk xfer", rec_size); |
1343 | | |
1344 | | // we should have enough data to parse the RPC record |
1345 | 3.60k | match parse_rpc_reply(cur_i, false) { |
1346 | 3.38k | Ok((_rem, ref hdr)) => { |
1347 | | // we got here because rec_size > input, so we should never have |
1348 | | // remaining data |
1349 | 3.38k | debug_validate_bug_on!(!_rem.is_empty()); |
1350 | | |
1351 | 3.38k | match parse_nfs3_reply_read(hdr.prog_data, false) { |
1352 | 2.84k | Ok((_, ref r)) => { |
1353 | | // deal with the partial nfs read data |
1354 | 2.84k | self.process_partial_read_reply_record(hdr, r); |
1355 | 2.84k | return AppLayerResult::ok(); |
1356 | | } |
1357 | 45 | Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { |
1358 | 45 | self.set_event(NFSEvent::MalformedData); |
1359 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1360 | 45 | return AppLayerResult::err(); |
1361 | | } |
1362 | 497 | Err(Err::Incomplete(_)) => { |
1363 | 497 | // this is normal, fall through to incomplete handling |
1364 | 497 | } |
1365 | | } |
1366 | | } |
1367 | 204 | Err(Err::Incomplete(_)) => { |
1368 | 204 | // size check was done for a minimal RPC record size, |
1369 | 204 | // so Incomplete is normal. |
1370 | 204 | SCLogDebug!("TC data incomplete"); |
1371 | 204 | } |
1372 | 7 | Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { |
1373 | 7 | self.set_event(NFSEvent::MalformedData); |
1374 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1375 | 7 | return AppLayerResult::err(); |
1376 | | } |
1377 | | } |
1378 | 18.6k | } |
1379 | 128k | } |
1380 | | // make sure we pass a value higher than current input |
1381 | | // but lower than the record size |
1382 | 148k | let n1 = cmp::max(cur_i.len(), 1024); |
1383 | 148k | let n2 = cmp::min(n1, rec_size); |
1384 | 148k | return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32); |
1385 | 150k | } |
1386 | | |
1387 | | /// Parsing function, handling TCP chunks fragmentation |
1388 | 598k | pub fn parse_tcp_data_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { |
1389 | 598k | let mut cur_i = stream_slice.as_slice(); |
1390 | | // take care of in progress file chunk transfers |
1391 | | // and skip buffer beyond it |
1392 | 598k | let consumed = self.filetracker_update(Direction::ToClient, cur_i, 0); |
1393 | 598k | if consumed > 0 { |
1394 | 38.4k | if consumed > cur_i.len() as u32 { |
1395 | 0 | return AppLayerResult::err(); |
1396 | 38.4k | } |
1397 | 38.4k | cur_i = &cur_i[consumed as usize..]; |
1398 | 559k | } |
1399 | 598k | if cur_i.is_empty() { |
1400 | 38.0k | return AppLayerResult::ok(); |
1401 | 560k | } |
1402 | 560k | if self.tc_gap { |
1403 | | SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len()); |
1404 | | |
1405 | 6.84k | let mut _cnt = 0; |
1406 | 4.07M | while !cur_i.is_empty() { |
1407 | 4.07M | _cnt += 1; |
1408 | 4.07M | match nfs_probe(cur_i, Direction::ToClient) { |
1409 | | 1 => { |
1410 | | SCLogDebug!("expected data found"); |
1411 | 86 | self.tc_gap = false; |
1412 | 86 | break; |
1413 | | }, |
1414 | | 0 => { |
1415 | | SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", |
1416 | | cur_i.len(), _cnt); |
1417 | 6.76k | return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32); |
1418 | | }, |
1419 | | -1 => { |
1420 | 4.06M | cur_i = &cur_i[1..]; |
1421 | 4.06M | if cur_i.is_empty() { |
1422 | 0 | SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt); |
1423 | 4.06M | } |
1424 | | }, |
1425 | | _ => { |
1426 | 0 | return AppLayerResult::err(); |
1427 | | } |
1428 | | } |
1429 | | } |
1430 | | SCLogDebug!("TC GAP handling done (input {})", cur_i.len()); |
1431 | 553k | } |
1432 | | |
1433 | 686k | while !cur_i.is_empty() { |
1434 | 666k | self.add_rpc_tcp_tc_pdu(flow, stream_slice, cur_i, cur_i.len() as i64); |
1435 | 666k | match parse_rpc_packet_header(cur_i) { |
1436 | 283k | Ok((_, ref rpc_phdr)) => { |
1437 | 283k | let rec_size = (rpc_phdr.frag_len + 4) as usize; |
1438 | | // see if we have all data available |
1439 | 283k | if rec_size > cur_i.len() { |
1440 | 150k | return self.parse_tcp_partial_data_tc(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size); |
1441 | 132k | } |
1442 | | |
1443 | | // we have the full data of the record, lets parse |
1444 | 132k | match parse_rpc_reply(cur_i, true) { |
1445 | 85.8k | Ok((_, ref rpc_record)) => { |
1446 | 85.8k | self.add_rpc_tcp_tc_frames(flow, stream_slice, cur_i, cur_i.len() as i64); |
1447 | 85.8k | self.process_reply_record(flow, stream_slice, rpc_record); |
1448 | 85.8k | } |
1449 | 4.07k | Err(Err::Incomplete(_)) => { |
1450 | 4.07k | // we shouldn't get incomplete as we have the full data |
1451 | 4.07k | // so if we got incomplete anyway it's the data that is |
1452 | 4.07k | // bad. |
1453 | 4.07k | self.set_event(NFSEvent::MalformedData); |
1454 | 4.07k | } |
1455 | 42.9k | Err(Err::Error(_e)) | |
1456 | 42.9k | Err(Err::Failure(_e)) => { |
1457 | 42.9k | self.set_event(NFSEvent::MalformedData); |
1458 | 42.9k | SCLogDebug!("Parsing failed: {:?}", _e); |
1459 | 42.9k | } |
1460 | | } |
1461 | 132k | cur_i = &cur_i[rec_size..]; // progress input past parsed record |
1462 | | } |
1463 | 380k | Err(Err::Incomplete(needed)) => { |
1464 | 380k | if let Needed::Size(n) = needed { |
1465 | | SCLogDebug!("Not enough data for partial RPC header {:?}", needed); |
1466 | | // 12 is the partial RPC header size parse_rpc_packet_header |
1467 | | // looks for. |
1468 | 380k | let n = usize::from(n); |
1469 | 380k | let need = if n > 12 { n } else { 12 }; |
1470 | 380k | return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32); |
1471 | 0 | } |
1472 | 0 | return AppLayerResult::err(); |
1473 | | } |
1474 | | /* This error is fatal. If we failed to parse the RPC hdr we don't |
1475 | | * have a length and we don't know where the next record starts. */ |
1476 | 1.32k | Err(Err::Error(_e)) | |
1477 | 0 | Err(Err::Failure(_e)) => { |
1478 | 1.32k | self.set_event(NFSEvent::MalformedData); |
1479 | | SCLogDebug!("Parsing failed: {:?}", _e); |
1480 | 1.32k | return AppLayerResult::err(); |
1481 | | } |
1482 | | } |
1483 | | }; |
1484 | 20.1k | self.post_gap_housekeeping(Direction::ToClient); |
1485 | 20.1k | if self.check_post_gap_file_txs && !self.post_gap_files_checked { |
1486 | 0 | self.post_gap_housekeeping_for_files(); |
1487 | 0 | self.post_gap_files_checked = true; |
1488 | 20.1k | } |
1489 | 20.1k | AppLayerResult::ok() |
1490 | 598k | } |
1491 | | /// Parsing function |
1492 | 988k | pub fn parse_udp_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { |
1493 | 988k | let input = stream_slice.as_slice(); |
1494 | | SCLogDebug!("parse_udp_ts ({})", input.len()); |
1495 | 988k | self.add_rpc_udp_ts_pdu(flow, stream_slice, input, input.len() as i64); |
1496 | 988k | if !input.is_empty() { |
1497 | 988k | match parse_rpc_udp_request(input) { |
1498 | 470k | Ok((_, ref rpc_record)) => { |
1499 | 470k | self.is_udp = true; |
1500 | 470k | self.add_rpc_udp_ts_creds(flow, stream_slice, &input[RPC_UDP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64); |
1501 | 470k | match rpc_record.progver { |
1502 | 400k | 3 => { |
1503 | 400k | self.process_request_record(flow, stream_slice, rpc_record); |
1504 | 400k | }, |
1505 | 37.3k | 2 => { |
1506 | 37.3k | self.add_nfs_ts_frame(flow, stream_slice, rpc_record.prog_data, rpc_record.prog_data_size as i64); |
1507 | 37.3k | self.process_request_record_v2(rpc_record); |
1508 | 37.3k | }, |
1509 | 33.0k | _ => { }, |
1510 | | } |
1511 | | }, |
1512 | 223k | Err(Err::Incomplete(_)) => { |
1513 | 223k | }, |
1514 | 293k | Err(Err::Error(_e)) | |
1515 | 293k | Err(Err::Failure(_e)) => { |
1516 | 293k | SCLogDebug!("Parsing failed: {:?}", _e); |
1517 | 293k | } |
1518 | | } |
1519 | 0 | } |
1520 | 988k | AppLayerResult::ok() |
1521 | 988k | } |
1522 | | |
1523 | | /// Parsing function |
1524 | 813k | pub fn parse_udp_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { |
1525 | 813k | let input = stream_slice.as_slice(); |
1526 | | SCLogDebug!("parse_udp_tc ({})", input.len()); |
1527 | 813k | self.add_rpc_udp_tc_pdu(flow, stream_slice, input, input.len() as i64); |
1528 | 813k | if !input.is_empty() { |
1529 | 813k | match parse_rpc_udp_reply(input) { |
1530 | 129k | Ok((_, ref rpc_record)) => { |
1531 | 129k | self.is_udp = true; |
1532 | 129k | self.add_rpc_udp_tc_frames(flow, stream_slice, input, input.len() as i64); |
1533 | 129k | self.process_reply_record(flow, stream_slice, rpc_record); |
1534 | 129k | }, |
1535 | 244k | Err(Err::Incomplete(_)) => { |
1536 | 244k | }, |
1537 | 439k | Err(Err::Error(_e)) | |
1538 | 439k | Err(Err::Failure(_e)) => { |
1539 | 439k | SCLogDebug!("Parsing failed: {:?}", _e); |
1540 | 439k | } |
1541 | | } |
1542 | 0 | } |
1543 | 813k | AppLayerResult::ok() |
1544 | 813k | } |
1545 | | } |
1546 | | |
1547 | | /// Returns *mut NFSState |
1548 | | #[no_mangle] |
1549 | 22.3k | pub extern "C" fn rs_nfs_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { |
1550 | 22.3k | let state = NFSState::new(); |
1551 | 22.3k | let boxed = Box::new(state); |
1552 | | SCLogDebug!("allocating state"); |
1553 | 22.3k | return Box::into_raw(boxed) as *mut _; |
1554 | 22.3k | } |
1555 | | |
1556 | | /// Params: |
1557 | | /// - state: *mut NFSState as void pointer |
1558 | | #[no_mangle] |
1559 | 22.3k | pub extern "C" fn rs_nfs_state_free(state: *mut std::os::raw::c_void) { |
1560 | | // Just unbox... |
1561 | | SCLogDebug!("freeing state"); |
1562 | 22.3k | std::mem::drop(unsafe { Box::from_raw(state as *mut NFSState) }); |
1563 | 22.3k | } |
1564 | | |
1565 | | /// C binding parse a NFS TCP request. Returns 1 on success, -1 on failure. |
1566 | | #[no_mangle] |
1567 | 651k | pub unsafe extern "C" fn rs_nfs_parse_request(flow: *const Flow, |
1568 | 651k | state: *mut std::os::raw::c_void, |
1569 | 651k | _pstate: *mut std::os::raw::c_void, |
1570 | 651k | stream_slice: StreamSlice, |
1571 | 651k | _data: *const std::os::raw::c_void, |
1572 | 651k | ) -> AppLayerResult |
1573 | | { |
1574 | 651k | let state = cast_pointer!(state, NFSState); |
1575 | 651k | let flow = cast_pointer!(flow, Flow); |
1576 | | |
1577 | 651k | if stream_slice.is_gap() { |
1578 | 134 | return rs_nfs_parse_request_tcp_gap(state, stream_slice.gap_size()); |
1579 | 651k | } |
1580 | | SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); |
1581 | | |
1582 | 651k | state.update_ts(flow.get_last_time().as_secs()); |
1583 | 651k | state.parse_tcp_data_ts(flow, &stream_slice) |
1584 | 651k | } |
1585 | | |
1586 | | #[no_mangle] |
1587 | 134 | pub extern "C" fn rs_nfs_parse_request_tcp_gap( |
1588 | 134 | state: &mut NFSState, |
1589 | 134 | input_len: u32) |
1590 | 134 | -> AppLayerResult |
1591 | | { |
1592 | 134 | state.parse_tcp_data_ts_gap(input_len) |
1593 | 134 | } |
1594 | | |
1595 | | #[no_mangle] |
1596 | 598k | pub unsafe extern "C" fn rs_nfs_parse_response(flow: *const Flow, |
1597 | 598k | state: *mut std::os::raw::c_void, |
1598 | 598k | _pstate: *mut std::os::raw::c_void, |
1599 | 598k | stream_slice: StreamSlice, |
1600 | 598k | _data: *const std::os::raw::c_void, |
1601 | 598k | ) -> AppLayerResult |
1602 | | { |
1603 | 598k | let state = cast_pointer!(state, NFSState); |
1604 | 598k | let flow = cast_pointer!(flow, Flow); |
1605 | | |
1606 | 598k | if stream_slice.is_gap() { |
1607 | 150 | return rs_nfs_parse_response_tcp_gap(state, stream_slice.gap_size()); |
1608 | 598k | } |
1609 | | SCLogDebug!("parsing {} bytes of response data", stream_slice.len()); |
1610 | | |
1611 | 598k | state.update_ts(flow.get_last_time().as_secs()); |
1612 | 598k | state.parse_tcp_data_tc(flow, &stream_slice) |
1613 | 598k | } |
1614 | | |
1615 | | #[no_mangle] |
1616 | 150 | pub extern "C" fn rs_nfs_parse_response_tcp_gap( |
1617 | 150 | state: &mut NFSState, |
1618 | 150 | input_len: u32) |
1619 | 150 | -> AppLayerResult |
1620 | | { |
1621 | 150 | state.parse_tcp_data_tc_gap(input_len) |
1622 | 150 | } |
1623 | | |
1624 | | /// C binding to parse an NFS/UDP request. Returns 1 on success, -1 on failure. |
1625 | | #[no_mangle] |
1626 | 988k | pub unsafe extern "C" fn rs_nfs_parse_request_udp(f: *const Flow, |
1627 | 988k | state: *mut std::os::raw::c_void, |
1628 | 988k | _pstate: *mut std::os::raw::c_void, |
1629 | 988k | stream_slice: StreamSlice, |
1630 | 988k | _data: *const std::os::raw::c_void, |
1631 | 988k | ) -> AppLayerResult |
1632 | | { |
1633 | 988k | let state = cast_pointer!(state, NFSState); |
1634 | | |
1635 | | SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); |
1636 | 988k | state.parse_udp_ts(f, &stream_slice) |
1637 | 988k | } |
1638 | | |
1639 | | #[no_mangle] |
1640 | 813k | pub unsafe extern "C" fn rs_nfs_parse_response_udp(f: *const Flow, |
1641 | 813k | state: *mut std::os::raw::c_void, |
1642 | 813k | _pstate: *mut std::os::raw::c_void, |
1643 | 813k | stream_slice: StreamSlice, |
1644 | 813k | _data: *const std::os::raw::c_void, |
1645 | 813k | ) -> AppLayerResult |
1646 | | { |
1647 | 813k | let state = cast_pointer!(state, NFSState); |
1648 | | SCLogDebug!("parsing {} bytes of response data", stream_slice.len()); |
1649 | 813k | state.parse_udp_tc(f, &stream_slice) |
1650 | 813k | } |
1651 | | |
1652 | | #[no_mangle] |
1653 | 9.19M | pub unsafe extern "C" fn rs_nfs_state_get_tx_count(state: *mut std::os::raw::c_void) |
1654 | 9.19M | -> u64 |
1655 | | { |
1656 | 9.19M | let state = cast_pointer!(state, NFSState); |
1657 | | SCLogDebug!("rs_nfs_state_get_tx_count: returning {}", state.tx_id); |
1658 | 9.19M | return state.tx_id; |
1659 | 9.19M | } |
1660 | | |
1661 | | #[no_mangle] |
1662 | 16.2k | pub unsafe extern "C" fn rs_nfs_state_get_tx(state: *mut std::os::raw::c_void, |
1663 | 16.2k | tx_id: u64) |
1664 | 16.2k | -> *mut std::os::raw::c_void |
1665 | | { |
1666 | 16.2k | let state = cast_pointer!(state, NFSState); |
1667 | 16.2k | match state.get_tx_by_id(tx_id) { |
1668 | 13.2k | Some(tx) => { |
1669 | 13.2k | return tx as *const _ as *mut _; |
1670 | | } |
1671 | | None => { |
1672 | 2.98k | return std::ptr::null_mut(); |
1673 | | } |
1674 | | } |
1675 | 16.2k | } |
1676 | | |
1677 | | #[no_mangle] |
1678 | 141k | pub unsafe extern "C" fn rs_nfs_state_tx_free(state: *mut std::os::raw::c_void, |
1679 | 141k | tx_id: u64) |
1680 | | { |
1681 | 141k | let state = cast_pointer!(state, NFSState); |
1682 | 141k | state.free_tx(tx_id); |
1683 | 141k | } |
1684 | | |
1685 | | #[no_mangle] |
1686 | 396M | pub unsafe extern "C" fn rs_nfs_tx_get_alstate_progress(tx: *mut std::os::raw::c_void, |
1687 | 396M | direction: u8) |
1688 | 396M | -> std::os::raw::c_int |
1689 | | { |
1690 | 396M | let tx = cast_pointer!(tx, NFSTransaction); |
1691 | 396M | if direction == Direction::ToServer.into() && tx.request_done { |
1692 | | SCLogDebug!("TOSERVER progress 1"); |
1693 | 1.04M | return 1; |
1694 | 395M | } else if direction == Direction::ToClient.into() && tx.response_done { |
1695 | | SCLogDebug!("TOCLIENT progress 1"); |
1696 | 28.4M | return 1; |
1697 | | } else { |
1698 | | SCLogDebug!("{} progress 0", direction); |
1699 | 366M | return 0; |
1700 | | } |
1701 | 396M | } |
1702 | | |
1703 | | #[no_mangle] |
1704 | 367M | pub unsafe extern "C" fn rs_nfs_get_tx_data( |
1705 | 367M | tx: *mut std::os::raw::c_void) |
1706 | 367M | -> *mut AppLayerTxData |
1707 | | { |
1708 | 367M | let tx = cast_pointer!(tx, NFSTransaction); |
1709 | 367M | return &mut tx.tx_data; |
1710 | 367M | } |
1711 | | |
1712 | | export_state_data_get!(rs_nfs_get_state_data, NFSState); |
1713 | | |
1714 | | /// return procedure(s) in the tx. At 0 return the main proc, |
1715 | | /// otherwise get procs from the 'file_additional_procs'. |
1716 | | /// Keep calling until 0 is returned. |
1717 | | #[no_mangle] |
1718 | 8.66k | pub unsafe extern "C" fn rs_nfs_tx_get_procedures(tx: &mut NFSTransaction, |
1719 | 8.66k | i: u16, |
1720 | 8.66k | procedure: *mut u32) |
1721 | 8.66k | -> u8 |
1722 | | { |
1723 | 8.66k | if i == 0 { |
1724 | 5.95k | *procedure = tx.procedure; |
1725 | 5.95k | return 1; |
1726 | 2.71k | } |
1727 | | |
1728 | 2.71k | if !tx.is_file_tx { |
1729 | 2.64k | return 0; |
1730 | 70 | } |
1731 | | |
1732 | | /* file tx handling follows */ |
1733 | | |
1734 | 70 | if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { |
1735 | 70 | let idx = i as usize - 1; |
1736 | 70 | if idx < tdf.file_additional_procs.len() { |
1737 | 0 | let p = tdf.file_additional_procs[idx]; |
1738 | 0 | *procedure = p; |
1739 | 0 | return 1; |
1740 | 70 | } |
1741 | 0 | } |
1742 | 70 | return 0; |
1743 | 8.66k | } |
1744 | | |
1745 | | #[no_mangle] |
1746 | 5.92k | pub unsafe extern "C" fn rs_nfs_tx_get_version(tx: &mut NFSTransaction, |
1747 | 5.92k | version: *mut u32) |
1748 | | { |
1749 | 5.92k | *version = tx.nfs_version as u32; |
1750 | 5.92k | } |
1751 | | |
1752 | | #[no_mangle] |
1753 | 68 | pub unsafe extern "C" fn rs_nfs_init(context: &'static mut SuricataFileContext) |
1754 | | { |
1755 | 68 | SURICATA_NFS_FILE_CONFIG = Some(context); |
1756 | 68 | } |
1757 | | |
1758 | 64.7k | fn nfs_probe_dir(i: &[u8], rdir: *mut u8) -> i8 { |
1759 | 64.7k | match parse_rpc_packet_header(i) { |
1760 | 60.7k | Ok((_, ref hdr)) => { |
1761 | 60.7k | let dir = if hdr.msgtype == 0 { |
1762 | 34.4k | Direction::ToServer |
1763 | | } else { |
1764 | 26.3k | Direction::ToClient |
1765 | | }; |
1766 | 60.7k | unsafe { *rdir = dir as u8 }; |
1767 | 60.7k | return 1; |
1768 | | }, |
1769 | | Err(Err::Incomplete(_)) => { |
1770 | 3.82k | return 0; |
1771 | | }, |
1772 | | Err(_) => { |
1773 | 112 | return -1; |
1774 | | }, |
1775 | | } |
1776 | 64.7k | } |
1777 | | |
1778 | 4.14M | pub fn nfs_probe(i: &[u8], direction: Direction) -> i32 { |
1779 | 4.14M | if direction == Direction::ToClient { |
1780 | 4.10M | match parse_rpc_reply(i, false) { |
1781 | 31.7k | Ok((_, ref rpc)) => { |
1782 | 31.7k | if rpc.hdr.frag_len >= 24 && rpc.hdr.frag_len <= 35000 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { |
1783 | | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); |
1784 | 12.0k | return 1; |
1785 | | } else { |
1786 | 19.7k | return -1; |
1787 | | } |
1788 | | }, |
1789 | | Err(Err::Incomplete(_)) => { |
1790 | 25.1k | match parse_rpc_packet_header (i) { |
1791 | 18.3k | Ok((_, ref rpc_hdr)) => { |
1792 | 18.3k | if rpc_hdr.frag_len >= 24 && rpc_hdr.frag_len <= 35000 && rpc_hdr.xid != 0 && rpc_hdr.msgtype == 1 { |
1793 | | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype); |
1794 | 11.8k | return 1; |
1795 | | } else { |
1796 | 6.54k | return -1; |
1797 | | } |
1798 | | }, |
1799 | | Err(Err::Incomplete(_)) => { |
1800 | 6.76k | return 0; |
1801 | | }, |
1802 | | Err(_) => { |
1803 | 0 | return -1; |
1804 | | }, |
1805 | | } |
1806 | | }, |
1807 | | Err(_) => { |
1808 | 4.04M | return -1; |
1809 | | }, |
1810 | | } |
1811 | | } else { |
1812 | 38.2k | match parse_rpc(i, false) { |
1813 | 18.8k | Ok((_, ref rpc)) => { |
1814 | 18.8k | if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 && |
1815 | 18.8k | rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) && |
1816 | 18.5k | rpc.program == 100003 && |
1817 | 18.5k | rpc.procedure <= NFSPROC3_COMMIT |
1818 | | { |
1819 | 18.5k | return rpc_auth_type_known(rpc.creds_flavor) as i32; |
1820 | | } else { |
1821 | 306 | return -1; |
1822 | | } |
1823 | | }, |
1824 | | Err(Err::Incomplete(_)) => { |
1825 | 15.6k | return 0; |
1826 | | }, |
1827 | | Err(_) => { |
1828 | 3.77k | return -1; |
1829 | | }, |
1830 | | } |
1831 | | } |
1832 | 4.14M | } |
1833 | | |
1834 | 2.30k | pub fn nfs_probe_udp(i: &[u8], direction: Direction) -> i32 { |
1835 | 2.30k | if direction == Direction::ToClient { |
1836 | 929 | match parse_rpc_udp_reply(i) { |
1837 | 733 | Ok((_, ref rpc)) => { |
1838 | 733 | if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { |
1839 | | SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); |
1840 | 477 | return 1; |
1841 | | } else { |
1842 | 256 | return -1; |
1843 | | } |
1844 | | }, |
1845 | | Err(_) => { |
1846 | 196 | return -1; |
1847 | | }, |
1848 | | } |
1849 | | } else { |
1850 | 1.37k | match parse_rpc_udp_request(i) { |
1851 | 853 | Ok((_, ref rpc)) => { |
1852 | 853 | if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 { |
1853 | 486 | return 1; |
1854 | 367 | } else if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 2 && rpc.program == 100003 { |
1855 | | SCLogDebug!("NFSv2!"); |
1856 | 74 | return 1; |
1857 | | } else { |
1858 | 293 | return -1; |
1859 | | } |
1860 | | }, |
1861 | | Err(_) => { |
1862 | 522 | return -1; |
1863 | | }, |
1864 | | } |
1865 | | } |
1866 | 2.30k | } |
1867 | | |
1868 | | /// MIDSTREAM |
1869 | | #[no_mangle] |
1870 | 64.7k | pub unsafe extern "C" fn rs_nfs_probe_ms( |
1871 | 64.7k | _flow: *const Flow, |
1872 | 64.7k | direction: u8, input: *const u8, |
1873 | 64.7k | len: u32, rdir: *mut u8) -> AppProto |
1874 | | { |
1875 | 64.7k | if input.is_null() { |
1876 | 77 | return ALPROTO_UNKNOWN; |
1877 | 64.7k | } |
1878 | 64.7k | let slice: &[u8] = build_slice!(input, len as usize); |
1879 | | SCLogDebug!("rs_nfs_probe_ms: probing direction {:02x}", direction); |
1880 | 64.7k | let mut adirection : u8 = 0; |
1881 | 64.7k | match nfs_probe_dir(slice, &mut adirection) { |
1882 | | 1 => { |
1883 | 60.7k | if adirection == Direction::ToServer.into() { |
1884 | 34.4k | SCLogDebug!("nfs_probe_dir said Direction::ToServer"); |
1885 | 34.4k | } else { |
1886 | 26.3k | SCLogDebug!("nfs_probe_dir said Direction::ToClient"); |
1887 | 26.3k | } |
1888 | 60.7k | match nfs_probe(slice, adirection.into()) { |
1889 | | 1 => { |
1890 | | SCLogDebug!("nfs_probe success: dir {:02x} adir {:02x}", direction, adirection); |
1891 | 42.1k | if (direction & DIR_BOTH) != adirection { |
1892 | 9.47k | *rdir = adirection; |
1893 | 32.7k | } |
1894 | 42.1k | ALPROTO_NFS |
1895 | | }, |
1896 | 15.5k | 0 => { ALPROTO_UNKNOWN }, |
1897 | 3.04k | _ => { ALPROTO_FAILED }, |
1898 | | } |
1899 | | }, |
1900 | | 0 => { |
1901 | 3.82k | ALPROTO_UNKNOWN |
1902 | | }, |
1903 | | _ => { |
1904 | 112 | ALPROTO_FAILED |
1905 | | } |
1906 | | } |
1907 | 64.7k | } |
1908 | | |
1909 | | #[no_mangle] |
1910 | 0 | pub unsafe extern "C" fn rs_nfs_probe(_f: *const Flow, |
1911 | 0 | direction: u8, |
1912 | 0 | input: *const u8, |
1913 | 0 | len: u32, |
1914 | 0 | _rdir: *mut u8) |
1915 | 0 | -> AppProto |
1916 | | { |
1917 | 0 | if input.is_null() { |
1918 | 0 | return ALPROTO_UNKNOWN; |
1919 | 0 | } |
1920 | 0 | let slice: &[u8] = build_slice!(input, len as usize); |
1921 | | SCLogDebug!("rs_nfs_probe: running probe"); |
1922 | 0 | match nfs_probe(slice, direction.into()) { |
1923 | 0 | 1 => { ALPROTO_NFS }, |
1924 | 0 | -1 => { ALPROTO_FAILED }, |
1925 | 0 | _ => { ALPROTO_UNKNOWN }, |
1926 | | } |
1927 | 0 | } |
1928 | | |
1929 | | /// TOSERVER probe function |
1930 | | #[no_mangle] |
1931 | 1.37k | pub unsafe extern "C" fn rs_nfs_probe_udp_ts(_f: *const Flow, |
1932 | 1.37k | _direction: u8, |
1933 | 1.37k | input: *const u8, |
1934 | 1.37k | len: u32, |
1935 | 1.37k | _rdir: *mut u8) |
1936 | 1.37k | -> AppProto |
1937 | | { |
1938 | 1.37k | if input.is_null() { |
1939 | 0 | return ALPROTO_UNKNOWN; |
1940 | 1.37k | } |
1941 | 1.37k | let slice: &[u8] = build_slice!(input, len as usize); |
1942 | 1.37k | match nfs_probe_udp(slice, Direction::ToServer) { |
1943 | 560 | 1 => { ALPROTO_NFS }, |
1944 | 815 | -1 => { ALPROTO_FAILED }, |
1945 | 0 | _ => { ALPROTO_UNKNOWN }, |
1946 | | } |
1947 | 1.37k | } |
1948 | | |
1949 | | /// TOCLIENT probe function |
1950 | | #[no_mangle] |
1951 | 929 | pub unsafe extern "C" fn rs_nfs_probe_udp_tc(_f: *const Flow, |
1952 | 929 | _direction: u8, |
1953 | 929 | input: *const u8, |
1954 | 929 | len: u32, |
1955 | 929 | _rdir: *mut u8) |
1956 | 929 | -> AppProto |
1957 | | { |
1958 | 929 | if input.is_null() { |
1959 | 0 | return ALPROTO_UNKNOWN; |
1960 | 929 | } |
1961 | 929 | let slice: &[u8] = build_slice!(input, len as usize); |
1962 | 929 | match nfs_probe_udp(slice, Direction::ToClient) { |
1963 | 477 | 1 => { ALPROTO_NFS }, |
1964 | 452 | -1 => { ALPROTO_FAILED }, |
1965 | 0 | _ => { ALPROTO_UNKNOWN }, |
1966 | | } |
1967 | 929 | } |
1968 | | |
1969 | | // Parser name as a C style string. |
1970 | | const PARSER_NAME: &[u8] = b"nfs\0"; |
1971 | | |
1972 | | #[no_mangle] |
1973 | 34 | pub unsafe extern "C" fn rs_nfs_register_parser() { |
1974 | 34 | let default_port = CString::new("[2049]").unwrap(); |
1975 | 34 | let parser = RustParser { |
1976 | 34 | name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
1977 | 34 | default_port: std::ptr::null(), |
1978 | 34 | ipproto: IPPROTO_TCP, |
1979 | 34 | probe_ts: None, |
1980 | 34 | probe_tc: None, |
1981 | 34 | min_depth: 0, |
1982 | 34 | max_depth: 16, |
1983 | 34 | state_new: rs_nfs_state_new, |
1984 | 34 | state_free: rs_nfs_state_free, |
1985 | 34 | tx_free: rs_nfs_state_tx_free, |
1986 | 34 | parse_ts: rs_nfs_parse_request, |
1987 | 34 | parse_tc: rs_nfs_parse_response, |
1988 | 34 | get_tx_count: rs_nfs_state_get_tx_count, |
1989 | 34 | get_tx: rs_nfs_state_get_tx, |
1990 | 34 | tx_comp_st_ts: 1, |
1991 | 34 | tx_comp_st_tc: 1, |
1992 | 34 | tx_get_progress: rs_nfs_tx_get_alstate_progress, |
1993 | 34 | get_eventinfo: Some(NFSEvent::get_event_info), |
1994 | 34 | get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id), |
1995 | 34 | localstorage_new: None, |
1996 | 34 | localstorage_free: None, |
1997 | 34 | get_tx_files: Some(rs_nfs_gettxfiles), |
1998 | 34 | get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>), |
1999 | 34 | get_tx_data: rs_nfs_get_tx_data, |
2000 | 34 | get_state_data: rs_nfs_get_state_data, |
2001 | 34 | apply_tx_config: None, |
2002 | 34 | flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, |
2003 | 34 | truncate: None, |
2004 | 34 | get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name), |
2005 | 34 | get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id), |
2006 | 34 | }; |
2007 | | |
2008 | 34 | let ip_proto_str = CString::new("tcp").unwrap(); |
2009 | | |
2010 | 34 | if AppLayerProtoDetectConfProtoDetectionEnabled( |
2011 | 34 | ip_proto_str.as_ptr(), |
2012 | 34 | parser.name, |
2013 | 34 | ) != 0 |
2014 | | { |
2015 | 34 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
2016 | 34 | ALPROTO_NFS = alproto; |
2017 | | |
2018 | 34 | let midstream = conf_get_bool("stream.midstream"); |
2019 | 34 | if midstream { |
2020 | 33 | if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_TCP, |
2021 | 33 | parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN, |
2022 | 33 | rs_nfs_probe_ms, rs_nfs_probe_ms) == 0 { |
2023 | 33 | SCLogDebug!("No NFSTCP app-layer configuration, enabling NFSTCP detection TCP detection on port {:?}.", |
2024 | 33 | default_port); |
2025 | 33 | /* register 'midstream' probing parsers if midstream is enabled. */ |
2026 | 33 | AppLayerProtoDetectPPRegister(IPPROTO_TCP, |
2027 | 33 | default_port.as_ptr(), ALPROTO_NFS, 0, |
2028 | 33 | NFS_MIN_FRAME_LEN, Direction::ToServer.into(), |
2029 | 33 | rs_nfs_probe_ms, rs_nfs_probe_ms); |
2030 | 33 | } |
2031 | 1 | } else { |
2032 | 1 | AppLayerProtoDetectPPRegister(IPPROTO_TCP, |
2033 | 1 | default_port.as_ptr(), ALPROTO_NFS, 0, |
2034 | 1 | NFS_MIN_FRAME_LEN, Direction::ToServer.into(), |
2035 | 1 | rs_nfs_probe, rs_nfs_probe); |
2036 | 1 | } |
2037 | 34 | if AppLayerParserConfParserEnabled( |
2038 | 34 | ip_proto_str.as_ptr(), |
2039 | 34 | parser.name, |
2040 | 34 | ) != 0 |
2041 | 34 | { |
2042 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
2043 | 34 | } |
2044 | | SCLogDebug!("Rust nfs parser registered."); |
2045 | 34 | let retval = conf_get("app-layer.protocols.nfs.max-requests"); |
2046 | 34 | if let Some(val) = retval { |
2047 | 0 | if let Ok(v) = val.parse::<usize>() { |
2048 | 0 | if v > 0 { |
2049 | 0 | NFS_CFG_MAX_REQ = v; |
2050 | 0 | } else { |
2051 | 0 | SCLogError!("Invalid max-requests value, must be >0"); |
2052 | | } |
2053 | | } else { |
2054 | 0 | SCLogError!("Invalid max-requests value"); |
2055 | | } |
2056 | 34 | } |
2057 | 34 | let retval = conf_get("app-layer.protocols.nfs.max-names"); |
2058 | 34 | if let Some(val) = retval { |
2059 | 0 | if let Ok(v) = val.parse::<usize>() { |
2060 | 0 | if v > 0 { |
2061 | 0 | NFS_CFG_MAX_NAMES = v; |
2062 | 0 | } else { |
2063 | 0 | SCLogError!("Invalid max-names value, must be >0"); |
2064 | | } |
2065 | | } else { |
2066 | 0 | SCLogError!("Invalid max-names value"); |
2067 | | } |
2068 | 34 | } |
2069 | 0 | } else { |
2070 | 0 | SCLogDebug!("Protocol detector and parser disabled for nfs."); |
2071 | 0 | } |
2072 | 34 | } |
2073 | | |
2074 | | #[no_mangle] |
2075 | 34 | pub unsafe extern "C" fn rs_nfs_udp_register_parser() { |
2076 | 34 | let default_port = CString::new("[2049]").unwrap(); |
2077 | 34 | let parser = RustParser { |
2078 | 34 | name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, |
2079 | 34 | default_port: std::ptr::null(), |
2080 | 34 | ipproto: IPPROTO_UDP, |
2081 | 34 | probe_ts: None, |
2082 | 34 | probe_tc: None, |
2083 | 34 | min_depth: 0, |
2084 | 34 | max_depth: 16, |
2085 | 34 | state_new: rs_nfs_state_new, |
2086 | 34 | state_free: rs_nfs_state_free, |
2087 | 34 | tx_free: rs_nfs_state_tx_free, |
2088 | 34 | parse_ts: rs_nfs_parse_request_udp, |
2089 | 34 | parse_tc: rs_nfs_parse_response_udp, |
2090 | 34 | get_tx_count: rs_nfs_state_get_tx_count, |
2091 | 34 | get_tx: rs_nfs_state_get_tx, |
2092 | 34 | tx_comp_st_ts: 1, |
2093 | 34 | tx_comp_st_tc: 1, |
2094 | 34 | tx_get_progress: rs_nfs_tx_get_alstate_progress, |
2095 | 34 | get_eventinfo: Some(NFSEvent::get_event_info), |
2096 | 34 | get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id), |
2097 | 34 | localstorage_new: None, |
2098 | 34 | localstorage_free: None, |
2099 | 34 | get_tx_files: Some(rs_nfs_gettxfiles), |
2100 | 34 | get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>), |
2101 | 34 | get_tx_data: rs_nfs_get_tx_data, |
2102 | 34 | get_state_data: rs_nfs_get_state_data, |
2103 | 34 | apply_tx_config: None, |
2104 | 34 | flags: 0, |
2105 | 34 | truncate: None, |
2106 | 34 | get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name), |
2107 | 34 | get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id), |
2108 | 34 | }; |
2109 | | |
2110 | 34 | let ip_proto_str = CString::new("udp").unwrap(); |
2111 | | |
2112 | 34 | if AppLayerProtoDetectConfProtoDetectionEnabled( |
2113 | 34 | ip_proto_str.as_ptr(), |
2114 | 34 | parser.name, |
2115 | 34 | ) != 0 |
2116 | | { |
2117 | 34 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
2118 | 34 | ALPROTO_NFS = alproto; |
2119 | | |
2120 | 34 | if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_UDP, |
2121 | 34 | parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN, |
2122 | 34 | rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc) == 0 { |
2123 | 34 | SCLogDebug!("No NFSUDP app-layer configuration, enabling NFSUDP detection UDP detection on port {:?}.", |
2124 | 34 | default_port); |
2125 | 34 | AppLayerProtoDetectPPRegister(IPPROTO_UDP, |
2126 | 34 | default_port.as_ptr(), ALPROTO_NFS, 0, |
2127 | 34 | NFS_MIN_FRAME_LEN, Direction::ToServer.into(), |
2128 | 34 | rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc); |
2129 | 34 | } |
2130 | 34 | if AppLayerParserConfParserEnabled( |
2131 | 34 | ip_proto_str.as_ptr(), |
2132 | 34 | parser.name, |
2133 | 34 | ) != 0 |
2134 | 34 | { |
2135 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
2136 | 34 | } |
2137 | 34 | if let Some(val) = conf_get("app-layer.protocols.nfs.max-tx") { |
2138 | 0 | if let Ok(v) = val.parse::<usize>() { |
2139 | 0 | NFS_MAX_TX = v; |
2140 | 0 | } else { |
2141 | 0 | SCLogError!("Invalid value for nfs.max-tx"); |
2142 | | } |
2143 | 34 | } |
2144 | | SCLogDebug!("Rust nfs parser registered."); |
2145 | 0 | } else { |
2146 | 0 | SCLogDebug!("Protocol detector and parser disabled for nfs."); |
2147 | 0 | } |
2148 | 34 | } |