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