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