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