]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
rust/nfs: NFSv3 parser, logger and detection
authorVictor Julien <victor@inliniac.net>
Mon, 22 May 2017 20:10:20 +0000 (22:10 +0200)
committerVictor Julien <victor@inliniac.net>
Tue, 6 Jun 2017 08:17:24 +0000 (10:17 +0200)
24 files changed:
rust/src/lib.rs
rust/src/nfs/log.rs [new file with mode: 0644]
rust/src/nfs/mod.rs [new file with mode: 0644]
rust/src/nfs/nfs3.rs [new file with mode: 0644]
rust/src/nfs/parser.rs [new file with mode: 0644]
rust/src/nfs/types.rs [new file with mode: 0644]
src/Makefile.am
src/app-layer-detect-proto.c
src/app-layer-nfs3.c [new file with mode: 0644]
src/app-layer-nfs3.h [new file with mode: 0644]
src/app-layer-parser.c
src/app-layer-protos.c
src/app-layer-protos.h
src/detect-filename.c
src/detect-nfs3-procedure.c [new file with mode: 0644]
src/detect-nfs3-procedure.h [new file with mode: 0644]
src/detect.c
src/detect.h
src/output-json-nfs3.c [new file with mode: 0644]
src/output-json-nfs3.h [new file with mode: 0644]
src/output.c
src/rust.h
src/suricata-common.h
suricata.yaml.in

index 4fc53b9ed254827436dba699f2dcab5bcb46cbf8..701df8655ea302c78d718e84a28fb37208e36300 100644 (file)
@@ -36,3 +36,4 @@ pub mod filetracker;
 pub mod lua;
 
 pub mod dns;
+pub mod nfs;
diff --git a/rust/src/nfs/log.rs b/rust/src/nfs/log.rs
new file mode 100644 (file)
index 0000000..bf98781
--- /dev/null
@@ -0,0 +1,110 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+extern crate libc;
+
+use std::string::String;
+use json::*;
+//use log::*;
+use nfs::types::*;
+use nfs::nfs3::*;
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_tx_logging_is_filtered(tx: &mut NFS3Transaction)
+                                                 -> libc::uint8_t
+{
+    // TODO probably best to make this configurable
+
+    if tx.procedure == NFSPROC3_GETATTR {
+        return 1;
+    }
+
+    return 0;
+}
+
+fn nfs3_creds_object(tx: &NFS3Transaction) -> Json
+{
+    let js = Json::object();
+    let mach_name = String::from_utf8_lossy(&tx.request_machine_name);
+    js.set_string("machine_name", &mach_name);
+    js.set_integer("uid", tx.request_uid as u64);
+    js.set_integer("gid", tx.request_gid as u64);
+    return js;
+}
+
+fn nfs3_write_object(tx: &NFS3Transaction) -> Json
+{
+    let js = Json::object();
+    js.set_boolean("first", tx.is_first);
+    js.set_boolean("last", tx.is_last);
+    js.set_integer("last_xid", tx.file_last_xid as u64);
+    return js;
+}
+
+fn nfs3_read_object(tx: &NFS3Transaction) -> Json
+{
+    let js = Json::object();
+    js.set_boolean("first", tx.is_first);
+    js.set_boolean("last", tx.is_last);
+    js.set_integer("last_xid", tx.file_last_xid as u64);
+    return js;
+}
+
+fn nfs3_common_header(tx: &NFS3Transaction) -> Json
+{
+    let js = Json::object();
+    js.set_integer("xid", tx.xid as u64);
+    js.set_string("procedure", &nfs3_procedure_string(tx.procedure));
+    let file_name = String::from_utf8_lossy(&tx.file_name);
+    js.set_string("filename", &file_name);
+    js.set_integer("id", tx.id as u64);
+    js.set_boolean("file_tx", tx.is_file_tx);
+    return js;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_log_json_request(tx: &mut NFS3Transaction) -> *mut JsonT
+{
+    let js = nfs3_common_header(tx);
+    js.set_string("type", "request");
+    return js.unwrap();
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_log_json_response(tx: &mut NFS3Transaction) -> *mut JsonT
+{
+    let js = nfs3_common_header(tx);
+    js.set_string("type", "response");
+
+    js.set_string("status", &nfs3_status_string(tx.response_status));
+
+    if tx.has_creds {
+        let creds_js = nfs3_creds_object(tx);
+        js.set("creds", creds_js);
+    }
+
+    if tx.procedure == NFSPROC3_READ {
+        let read_js = nfs3_read_object(tx);
+        js.set("read", read_js);
+    }
+    if tx.procedure == NFSPROC3_WRITE {
+        let write_js = nfs3_write_object(tx);
+        js.set("write", write_js);
+    }
+
+    return js.unwrap();
+}
diff --git a/rust/src/nfs/mod.rs b/rust/src/nfs/mod.rs
new file mode 100644 (file)
index 0000000..275dee9
--- /dev/null
@@ -0,0 +1,25 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod types;
+#[macro_use]
+pub mod parser;
+pub mod nfs3;
+pub mod log;
+
+//#[cfg(feature = "lua")]
+//pub mod lua;
diff --git a/rust/src/nfs/nfs3.rs b/rust/src/nfs/nfs3.rs
new file mode 100644 (file)
index 0000000..9401970
--- /dev/null
@@ -0,0 +1,1399 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// written by Victor Julien
+// TCP buffering code written by Pierre Chifflier
+
+extern crate libc;
+use std;
+use std::mem::transmute;
+
+use nom::IResult;
+
+use std::collections::{HashMap};
+
+use nom;
+use log::*;
+use applayer::LoggerFlags;
+use core::*;
+use filetracker::*;
+use filecontainer::*;
+//use storage::*;
+
+use nfs::types::*;
+use nfs::parser::*;
+
+/// nom bug leads to this wrappers being necessary
+/// TODO for some reason putting these in parser.rs and making them public
+/// leads to a compile error wrt an unknown lifetime identifier 'a
+//named!(many0_nfs3_request_objects<Vec<Nfs3RequestObject<'a>>>, many0!(parse_nfs3_request_object));
+//named!(many0_nfs3_reply_objects<Vec<Nfs3ReplyObject<'a>>>, many0!(parse_nfs3_reply_object));
+named!(many0_nfs3_response_readdirplus_entries<Vec<Nfs3ResponseReaddirplusEntry<'a>>>,
+        many0!(parse_nfs3_response_readdirplus_entry_cond));
+
+pub static mut suricata_nfs3_file_config: Option<&'static SuricataFileContext> = None;
+
+/*
+ * Record parsing.
+ *
+ * Incomplete records come in due to TCP splicing. For all record types
+ * except READ and WRITE, processing only begins when the full record
+ * is available. For READ/WRITE partial records are processed as well to
+ * avoid queuing too much data.
+ *
+ * Getting file names.
+ *
+ * NFS makes heavy use of 'file handles' for operations. In many cases it
+ * uses a file name just once and after that just the handle. For example,
+ * if a client did a file listing (e.g. READDIRPLUS) and would READ the
+ * file afterwards, the name will only appear in the READDIRPLUS answer.
+ * To be able to log the names we store a mapping between file handles
+ * and file names in NFS3State::namemap.
+ *
+ * Mapping NFS to Suricata's transaction model.
+ *
+ * The easiest way to do transactions would be to map each command/reply with
+ * the same XID to a transaction. This would allow for per XID logging, detect
+ * etc. However this model doesn't fit well with file tracking. The file
+ * tracking in Suricata is really expecting to be one or more files to live
+ * inside a single transaction. Would XID pairs be a transaction however,
+ * there would be many transactions forming a single file. This will be very
+ * inefficient.
+ *
+ * The model implemented here is as follows: each file transfer is a single
+ * transaction. All XID pairs unrelated to those file transfers create
+ * transactions per pair.
+ *
+ * A complicating factor is that the procedure matching is per tx, and a
+ * file transfer may have multiple procedures involved. Currently now only
+ * a COMMIT after WRITEs. A vector of additional procedures is kept to
+ * match on this.
+ *
+ * File tracking
+ *
+ * Files are tracked per 'FileTransferTracker' and are stored in the
+ * NFS3Transaction where they can be looked up per handle as part of the
+ * Transaction lookup.
+ */
+
+
+#[derive(Debug)]
+pub struct NFS3Transaction {
+    pub id: u64,    /// internal id
+    pub xid: u32,   /// nfs3 req/reply pair id
+    pub procedure: u32,
+    pub file_name: Vec<u8>,
+
+    pub request_machine_name: Vec<u8>,
+    pub request_uid: u32,
+    pub request_gid: u32,
+
+    pub response_status: u32,
+
+    pub has_creds: bool,
+    pub is_first: bool,
+    pub is_last: bool,
+
+    /// for state tracking. false means this side is in progress, true
+    /// that it's complete.
+    request_done: bool,
+    response_done: bool,
+
+    /// is a special file tx that we look up by file_handle instead of XID
+    pub is_file_tx: bool,
+    /// file transactions are unidirectional in the sense that they track
+    /// a single file on one direction
+    pub file_tx_direction: u8, // STREAM_TOCLIENT or STREAM_TOSERVER
+    pub file_handle: Vec<u8>,
+
+    /// additional procedures part of a single file transfer. Currently
+    /// only COMMIT on WRITEs.
+    pub file_additional_procs: Vec<u32>,
+
+    /// last xid of this file transfer. Last READ or COMMIT normally.
+    pub file_last_xid: u32,
+
+    /// file tracker for a single file. Boxed so that we don't use
+    /// as much space if we're not a file tx.
+    pub file_tracker: Option<Box<FileTransferTracker>>,
+
+    pub logged: LoggerFlags,
+    pub de_state: Option<*mut DetectEngineState>,
+    pub events: *mut AppLayerDecoderEvents,
+}
+
+impl NFS3Transaction {
+    pub fn new() -> NFS3Transaction {
+        return NFS3Transaction{
+            id: 0,
+            xid: 0,
+            procedure: 0,
+            file_name:Vec::new(),
+            request_machine_name:Vec::new(),
+            request_uid:0,
+            request_gid:0,
+            response_status:0,
+            has_creds: false,
+            is_first: false,
+            is_last: false,
+            request_done: false,
+            response_done: false,
+            is_file_tx: false,
+            file_tx_direction: 0,
+            file_handle:Vec::new(),
+            file_additional_procs:Vec::new(),
+            file_last_xid: 0,
+            file_tracker: None,
+            logged: LoggerFlags::new(),
+            de_state: None,
+            events: std::ptr::null_mut(),
+        }
+    }
+
+    pub fn free(&mut self) {
+        if self.events != std::ptr::null_mut() {
+            sc_app_layer_decoder_events_free_events(&mut self.events);
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct NFS3RequestXidMap {
+    procedure: u32,
+    chunk_offset: u64,
+    file_name:Vec<u8>,
+
+    /// READ replies can use this to get to the handle the request used
+    file_handle:Vec<u8>,
+}
+
+impl NFS3RequestXidMap {
+    pub fn new(procedure: u32, chunk_offset: u64) -> NFS3RequestXidMap {
+        NFS3RequestXidMap {
+            procedure:procedure, chunk_offset:chunk_offset,
+            file_name:Vec::new(),
+            file_handle:Vec::new(),
+        }
+    }
+}
+
+#[derive(Debug)]
+pub struct NFS3Files {
+    pub files_ts: FileContainer,
+    pub files_tc: FileContainer,
+    pub flags_ts: u16,
+    pub flags_tc: u16,
+}
+
+impl NFS3Files {
+    pub fn new() -> NFS3Files {
+        NFS3Files {
+            files_ts:FileContainer::default(),
+            files_tc:FileContainer::default(),
+            flags_ts:0,
+            flags_tc:0,
+        }
+    }
+    pub fn free(&mut self) {
+        self.files_ts.free();
+        self.files_tc.free();
+    }
+
+    pub fn get(&mut self, direction: u8) -> (&mut FileContainer, u16)
+    {
+        if direction == STREAM_TOSERVER {
+            (&mut self.files_ts, self.flags_ts)
+        } else {
+            (&mut self.files_tc, self.flags_tc)
+        }
+    }
+}
+
+/// little wrapper around the FileTransferTracker::new_chunk method
+fn filetracker_newchunk(ftopt: &mut Option<Box<FileTransferTracker>>, files: &mut FileContainer,
+        flags: u16, name: &Vec<u8>, data: &[u8],
+        chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32)
+{
+    match ftopt {
+        &mut Some(ref mut ft) => {
+            match unsafe {suricata_nfs3_file_config} {
+                Some(sfcm) => {
+                    ft.new_chunk(sfcm, files, flags, &name, data, chunk_offset,
+                            chunk_size, fill_bytes, is_last, xid); }
+                None => panic!("BUG"),
+            }
+        },
+        &mut None => { panic!("BUG"); },
+    }
+}
+
+#[derive(Debug)]
+pub struct NFS3State {
+    /// map xid to procedure so replies can lookup the procedure
+    pub requestmap: HashMap<u32, NFS3RequestXidMap>,
+
+    /// map file handle (1) to name (2)
+    pub namemap: HashMap<Vec<u8>, Vec<u8>>,
+
+    /// transactions list
+    pub transactions: Vec<NFS3Transaction>,
+
+    /// TCP segments defragmentation buffer
+    pub tcp_buffer_ts: Vec<u8>,
+    pub tcp_buffer_tc: Vec<u8>,
+
+    pub files: NFS3Files,
+
+    /// partial record tracking
+    ts_chunk_xid: u32,
+    tc_chunk_xid: u32,
+    /// size of the current chunk that we still need to receive
+    ts_chunk_left: u32,
+    tc_chunk_left: u32,
+
+    /// tx counter for assigning incrementing id's to tx's
+    tx_id: u64,
+
+    pub de_state_count: u64,
+
+    // HACK flag state if tx has been marked complete in a direction
+    // this way we can skip a lot of looping in output-tx.c
+    //pub ts_txs_updated: bool,
+    //pub tc_txs_updated: bool,
+}
+
+impl NFS3State {
+    /// Allocation function for a new TLS parser instance
+    pub fn new() -> NFS3State {
+        NFS3State {
+            requestmap:HashMap::new(),
+            namemap:HashMap::new(),
+            transactions: Vec::new(),
+            tcp_buffer_ts:Vec::with_capacity(8192),
+            tcp_buffer_tc:Vec::with_capacity(8192),
+            files:NFS3Files::new(),
+            ts_chunk_xid:0,
+            tc_chunk_xid:0,
+            ts_chunk_left:0,
+            tc_chunk_left:0,
+            tx_id:0,
+            de_state_count:0,
+            //ts_txs_updated:false,
+            //tc_txs_updated:false,
+        }
+    }
+    pub fn free(&mut self) {
+        self.files.free();
+    }
+
+    pub fn new_tx(&mut self) -> NFS3Transaction {
+        let mut tx = NFS3Transaction::new();
+        self.tx_id += 1;
+        tx.id = self.tx_id;
+        return tx;
+    }
+
+    pub fn free_tx(&mut self, tx_id: u64) {
+        //SCLogNotice!("Freeing TX with ID {}", tx_id);
+        let len = self.transactions.len();
+        let mut found = false;
+        let mut index = 0;
+        for i in 0..len {
+            let tx = &self.transactions[i];
+            if tx.id == tx_id + 1 {
+                found = true;
+                index = i;
+                break;
+            }
+        }
+        if found {
+            SCLogDebug!("freeing TX with ID {} at index {}", tx_id, index);
+            self.free_tx_at_index(index);
+        }
+    }
+
+    fn free_tx_at_index(&mut self, index: usize) {
+        let tx = self.transactions.remove(index);
+        match tx.de_state {
+            Some(state) => {
+                sc_detect_engine_state_free(state);
+                self.de_state_count -= 1;
+            }
+            _ => {}
+        }
+    }
+
+    pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NFS3Transaction> {
+        SCLogDebug!("get_tx_by_id: tx_id={}", tx_id);
+        for tx in &mut self.transactions {
+            if tx.id == tx_id + 1 {
+                SCLogDebug!("Found NFS3 TX with ID {}", tx_id);
+                return Some(tx);
+            }
+        }
+        SCLogDebug!("Failed to find NFS3 TX with ID {}", tx_id);
+        return None;
+    }
+
+    pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFS3Transaction> {
+        SCLogDebug!("get_tx_by_xid: tx_xid={}", tx_xid);
+        for tx in &mut self.transactions {
+            if !tx.is_file_tx && tx.xid == tx_xid {
+                SCLogDebug!("Found NFS3 TX with ID {} XID {}", tx.id, tx.xid);
+                return Some(tx);
+            }
+        }
+        SCLogDebug!("Failed to find NFS3 TX with XID {}", tx_xid);
+        return None;
+    }
+
+    // TODO maybe not enough users to justify a func
+    fn mark_response_tx_done(&mut self, xid: u32, status: u32)
+    {
+        match self.get_tx_by_xid(xid) {
+            Some(mut mytx) => {
+                mytx.response_done = true;
+                mytx.response_status = status;
+
+                SCLogDebug!("process_reply_record: tx ID {} XID {} REQUEST {} RESPONSE {}",
+                        mytx.id, mytx.xid, mytx.request_done, mytx.response_done);
+            },
+            None => {
+                //SCLogNotice!("process_reply_record: not TX found for XID {}", r.hdr.xid);
+            },
+        }
+
+        //self.tc_txs_updated = true;
+    }
+
+    fn process_request_record_lookup<'b>(&mut self, r: &RpcPacket<'b>, xidmap: &mut NFS3RequestXidMap) {
+        match parse_nfs3_request_lookup(r.prog_data) {
+            IResult::Done(_, lookup) => {
+                SCLogDebug!("LOOKUP {:?}", lookup);
+                xidmap.file_name = lookup.name_vec;
+            },
+            IResult::Incomplete(_) => { panic!("WEIRD"); },
+            IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+        };
+    }
+
+    /// complete request record
+    fn process_request_record<'b>(&mut self, r: &RpcPacket<'b>) -> u32 {
+        SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}",
+                r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+        let mut xidmap = NFS3RequestXidMap::new(r.procedure, 0);
+
+        if r.procedure == NFSPROC3_LOOKUP {
+            self.process_request_record_lookup(r, &mut xidmap);
+
+        } else if r.procedure == NFSPROC3_READ {
+            match parse_nfs3_request_read(r.prog_data) {
+                IResult::Done(_, nfs3_read_record) => {
+                    xidmap.chunk_offset = nfs3_read_record.offset;
+                    xidmap.file_handle = nfs3_read_record.handle.value.to_vec();
+
+                    match self.namemap.get(nfs3_read_record.handle.value) {
+                        Some(n) => {
+                            SCLogDebug!("READ name {:?}", n);
+                            xidmap.file_name = n.to_vec();
+                        },
+                        _ => {
+                            SCLogDebug!("READ object {:?} not found",
+                                    nfs3_read_record.handle.value);
+                        },
+                    }
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        } else if r.procedure == NFSPROC3_WRITE {
+            match parse_nfs3_request_write(r.prog_data) {
+                IResult::Done(_, w) => {
+                    self.process_write_record(r, &w);
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            }
+        } else if r.procedure == NFSPROC3_CREATE {
+            match parse_nfs3_request_create(r.prog_data) {
+                IResult::Done(_, nfs3_create_record) => {
+                    xidmap.file_name = nfs3_create_record.name_vec;
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+
+        } else if r.procedure == NFSPROC3_COMMIT {
+            SCLogDebug!("COMMIT, closing shop");
+
+            match parse_nfs3_request_commit(r.prog_data) {
+                IResult::Done(_, cr) => {
+                    let file_handle = cr.handle.value.to_vec();
+                    match self.get_file_tx_by_handle(&file_handle, STREAM_TOSERVER) {
+                        Some((tx, files, flags)) => {
+                            match tx.file_tracker {
+                                Some(ref mut sft) => {  // mutable reference to md for updating
+                                    sft.close(files, flags);
+                                },
+                                None => { },
+                            }
+                            tx.file_last_xid = r.hdr.xid;
+                            tx.request_done = true;
+                            tx.file_additional_procs.push(NFSPROC3_COMMIT);
+                        },
+                        None => { },
+                    }
+                    //self.ts_txs_updated = true;
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        }
+
+        if !(r.procedure == NFSPROC3_COMMIT || // commit handled separately
+             r.procedure == NFSPROC3_WRITE  || // write handled in file tx
+             r.procedure == NFSPROC3_READ)     // read handled in file tx at reply
+        {
+            let mut tx = self.new_tx();
+            tx.xid = r.hdr.xid;
+            tx.procedure = r.procedure;
+            tx.request_done = true;
+            tx.file_name = xidmap.file_name.to_vec();
+            //self.ts_txs_updated = true;
+
+            match &r.creds_unix {
+                &Some(ref u) => {
+                    tx.request_machine_name = u.machine_name_buf.to_vec();
+                    tx.request_uid = u.uid;
+                    tx.request_gid = u.gid;
+                    tx.has_creds = true;
+                },
+                _ => { },
+            }
+            SCLogDebug!("TX created: ID {} XID {} PROCEDURE {}",
+                    tx.id, tx.xid, tx.procedure);
+            self.transactions.push(tx);
+        }
+
+        self.requestmap.insert(r.hdr.xid, xidmap);
+        0
+    }
+
+    fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
+        -> (&mut NFS3Transaction, &mut FileContainer, u16)
+    {
+        let mut tx = self.new_tx();
+        tx.file_name = file_name.to_vec();
+        tx.file_handle = file_handle.to_vec();
+        tx.is_file_tx = true;
+        tx.file_tx_direction = direction;
+
+        tx.file_tracker = Some(Box::new(FileTransferTracker::new()));
+        match tx.file_tracker {
+            Some(ref mut ft) => { ft.tx_id = tx.id; },
+            None => { },
+        }
+
+        SCLogDebug!("new_file_tx: TX FILE created: ID {} NAME {}",
+                tx.id, String::from_utf8_lossy(file_name));
+        self.transactions.push(tx);
+        let tx_ref = self.transactions.last_mut();
+        let (files, flags) = self.files.get(direction);
+        return (tx_ref.unwrap(), files, flags)
+    }
+
+    fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8)
+        -> Option<(&mut NFS3Transaction, &mut FileContainer, u16)>
+    {
+        let fh = file_handle.to_vec();
+        for tx in &mut self.transactions {
+            if tx.is_file_tx &&
+                direction == tx.file_tx_direction &&
+                tx.file_handle == fh
+            {
+                SCLogDebug!("Found NFS3 file TX with ID {} XID {}", tx.id, tx.xid);
+                let (files, flags) = self.files.get(direction);
+                return Some((tx, files, flags));
+            }
+        }
+        SCLogDebug!("Failed to find NFS3 TX with handle {:?}", file_handle);
+        return None;
+    }
+
+    fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 {
+        // for now assume that stable FILE_SYNC flags means a single chunk
+        let is_last = if w.stable == 2 { true } else { false };
+
+        let mut fill_bytes = 0;
+        let pad = w.file_len % 4;
+        if pad != 0 {
+            fill_bytes = 4 - pad;
+        }
+
+        let file_handle = w.handle.value.to_vec();
+        let file_name = match self.namemap.get(w.handle.value) {
+            Some(n) => {
+                SCLogDebug!("WRITE name {:?}", n);
+                n.to_vec()
+            },
+            None => {
+                SCLogDebug!("WRITE object {:?} not found", w.handle.value);
+                Vec::new()
+            },
+        };
+
+        let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOSERVER) {
+            Some((tx, files, flags)) => {
+                filetracker_newchunk(&mut tx.file_tracker, files, flags,
+                        &file_name, w.file_data, w.offset,
+                        w.file_len, fill_bytes as u8, is_last, &r.hdr.xid);
+                if is_last {
+                    tx.file_last_xid = r.hdr.xid;
+                    tx.is_last = true;
+                    tx.response_done = true;
+                }
+                true
+            },
+            None => { false },
+        };
+        if !found {
+            let (tx, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOSERVER);
+            filetracker_newchunk(&mut tx.file_tracker, files, flags,
+                    &file_name, w.file_data, w.offset,
+                    w.file_len, fill_bytes as u8, is_last, &r.hdr.xid);
+            tx.procedure = NFSPROC3_WRITE;
+            tx.xid = r.hdr.xid;
+            tx.is_first = true;
+            if is_last {
+                tx.file_last_xid = r.hdr.xid;
+                tx.is_last = true;
+                tx.request_done = true;
+            }
+        }
+        self.ts_chunk_xid = r.hdr.xid;
+        let file_data_len = w.file_data.len() as u32 - fill_bytes as u32;
+        self.ts_chunk_left = w.file_len as u32 - file_data_len as u32;
+        0
+    }
+
+    fn process_partial_write_request_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 {
+        SCLogDebug!("REQUEST {} procedure {} blob size {}", r.hdr.xid, r.procedure, r.prog_data.len());
+
+        if r.procedure != NFSPROC3_WRITE {
+            panic!("call me for procedure WRITE *only*");
+        }
+
+        let mut xidmap = NFS3RequestXidMap::new(r.procedure, 0);
+        xidmap.file_handle = w.handle.value.to_vec();
+        self.requestmap.insert(r.hdr.xid, xidmap);
+
+        return self.process_write_record(r, w);
+    }
+
+    fn process_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>) -> u32 {
+        let status;
+        let xidmap;
+        match self.requestmap.remove(&r.hdr.xid) {
+            Some(p) => { xidmap = p; },
+            _ => { SCLogDebug!("REPLY: xid {} NOT FOUND", r.hdr.xid); return 0; },
+        }
+
+        if xidmap.procedure == NFSPROC3_LOOKUP {
+            match parse_nfs3_response_lookup(r.prog_data) {
+                IResult::Done(_, lookup) => {
+                    SCLogDebug!("LOOKUP: {:?}", lookup);
+                    SCLogDebug!("RESPONSE LOOKUP file_name {:?}", xidmap.file_name);
+
+                    status = lookup.status;
+
+                    SCLogDebug!("LOOKUP handle {:?}", lookup.handle);
+                    self.namemap.insert(lookup.handle.value.to_vec(), xidmap.file_name);
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        } else if xidmap.procedure == NFSPROC3_CREATE {
+            match parse_nfs3_response_create(r.prog_data) {
+                IResult::Done(_, nfs3_create_record) => {
+                    SCLogDebug!("nfs3_create_record: {:?}", nfs3_create_record);
+
+                    SCLogDebug!("RESPONSE CREATE file_name {:?}", xidmap.file_name);
+                    status = nfs3_create_record.status;
+
+                    match nfs3_create_record.handle {
+                        Some(h) => {
+                            SCLogDebug!("handle {:?}", h);
+                            self.namemap.insert(h.value.to_vec(), xidmap.file_name);
+                        },
+                        _ => { },
+                    }
+
+                },
+                IResult::Incomplete(_) => { panic!("WEIRD"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e);  },
+            };
+        } else if xidmap.procedure == NFSPROC3_READ {
+            match parse_nfs3_reply_read(r.prog_data) {
+                IResult::Done(_, ref reply) => {
+                    self.process_read_record(r, reply, Some(&xidmap));
+                    status = reply.status;
+                },
+                IResult::Incomplete(_) => { panic!("Incomplete!"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+            }
+        } else if xidmap.procedure == NFSPROC3_READDIRPLUS {
+            match parse_nfs3_response_readdirplus(r.prog_data) {
+                IResult::Done(_, ref reply) => {
+                    //SCLogDebug!("READDIRPLUS reply {:?}", reply);
+
+                    status = reply.status;
+
+                    // cut off final eof field
+                    let d = &reply.data[..reply.data.len()-4 as usize];
+
+                    // store all handle/filename mappings
+                    match many0_nfs3_response_readdirplus_entries(d) {
+                        IResult::Done(_, ref entries) => {
+                            for ce in entries {
+                                SCLogDebug!("ce {:?}", ce);
+                                match ce.entry {
+                                    Some(ref e) => {
+                                        SCLogDebug!("e {:?}", e);
+                                        match e.handle {
+                                            Some(ref h) => {
+                                                SCLogDebug!("h {:?}", h);
+                                                self.namemap.insert(h.value.to_vec(), e.name_vec.to_vec());
+                                            },
+                                            _ => { },
+                                        }
+                                    },
+                                    _ => { },
+                                }
+                            }
+
+                            SCLogDebug!("READDIRPLUS ENTRIES reply {:?}", entries);
+                        },
+                        IResult::Incomplete(_) => { panic!("Incomplete!"); },
+                        IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+                    }
+                },
+                IResult::Incomplete(_) => { panic!("Incomplete!"); },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+            }
+        }
+        // for all other record types only parse the status
+        else {
+            let stat = match nom::be_u32(&r.prog_data) {
+                nom::IResult::Done(_, stat) => {
+                    stat as u32
+                }
+                _ => 0 as u32
+            };
+            status = stat;
+        }
+        SCLogDebug!("REPLY {} to procedure {} blob size {}",
+                r.hdr.xid, xidmap.procedure, r.prog_data.len());
+
+        if xidmap.procedure != NFSPROC3_READ {
+            self.mark_response_tx_done(r.hdr.xid, status);
+        }
+
+        0
+    }
+
+    // update in progress chunks for file transfers
+    // return how much data we consumed
+    fn filetracker_update(&mut self, direction: u8, data: &[u8]) -> u32 {
+        let mut chunk_left = if direction == STREAM_TOSERVER {
+            self.ts_chunk_left
+        } else {
+            self.tc_chunk_left
+        };
+        if chunk_left == 0 {
+            return 0
+        }
+        let xid = if direction == STREAM_TOSERVER {
+            self.ts_chunk_xid
+        } else {
+            self.tc_chunk_xid
+        };
+        SCLogDebug!("chunk left {}, input {}", chunk_left, data.len());
+
+        let file_handle;
+        // we have the data that we expect
+        if chunk_left <= data.len() as u32 {
+            chunk_left = 0;
+
+            if direction == STREAM_TOSERVER {
+                self.ts_chunk_xid = 0;
+
+                // see if we have a file handle to work on
+                match self.requestmap.get(&xid) {
+                    None => {
+                        SCLogDebug!("no file handle found for XID {:04X}", xid);
+                        return 0
+                    },
+                    Some(ref xidmap) => {
+                        file_handle = xidmap.file_handle.to_vec();
+                    },
+                }
+            } else {
+                self.tc_chunk_xid = 0;
+
+                // chunk done, remove requestmap entry
+                match self.requestmap.remove(&xid) {
+                    None => {
+                        SCLogDebug!("no file handle found for XID {:04X}", xid);
+                        return 0
+                    },
+                    Some(xidmap) => {
+                        file_handle = xidmap.file_handle.to_vec();
+                    },
+                }
+            }
+        } else {
+            chunk_left -= data.len() as u32;
+
+            // see if we have a file handle to work on
+            match self.requestmap.get(&xid) {
+                None => {
+                    SCLogDebug!("no file handle found for XID {:04X}", xid);
+                    return 0 },
+                Some(xidmap) => {
+                    file_handle = xidmap.file_handle.to_vec();
+                },
+            }
+        }
+
+        if direction == STREAM_TOSERVER {
+            self.ts_chunk_left = chunk_left;
+        } else {
+            self.tc_chunk_left = chunk_left;
+        }
+
+        // get the tx and update it
+        let consumed = match self.get_file_tx_by_handle(&file_handle, direction) {
+            Some((tx, files, flags)) => {
+                let cs1 = match tx.file_tracker {
+                    Some(ref mut ft) => {
+                        let cs2 = ft.update(files, flags, data);
+                        // return number of bytes we consumed
+                        SCLogDebug!("consumed {}", cs2);
+                        cs2
+                    },
+                    None => { 0 },
+                };
+                cs1
+            },
+            None => { 0 },
+        };
+        return consumed;
+    }
+
+    /// xidmapr is an Option as it's already removed from the map if we
+    /// have a complete record. Otherwise we do a lookup ourselves.
+    fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>,
+            reply: &Nfs3ReplyRead<'b>, xidmapr: Option<&NFS3RequestXidMap>) -> u32
+    {
+        let file_name;
+        let file_handle;
+        let chunk_offset;
+
+        match xidmapr {
+            Some(xidmap) => {
+                file_name = xidmap.file_name.to_vec();
+                file_handle = xidmap.file_handle.to_vec();
+                chunk_offset = xidmap.chunk_offset;
+            },
+            None => {
+                match self.requestmap.get(&r.hdr.xid) {
+                    Some(xidmap) => {
+                        file_name = xidmap.file_name.to_vec();
+                        file_handle = xidmap.file_handle.to_vec();
+                        chunk_offset = xidmap.chunk_offset;
+                    },
+                    _ => { panic!("REPLY: xid {} NOT FOUND", r.hdr.xid); },
+                }
+            },
+        }
+
+        let is_last = reply.eof;
+        let mut fill_bytes = 0;
+        let pad = reply.count % 4;
+        if pad != 0 {
+            fill_bytes = 4 - pad;
+        }
+
+        let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOCLIENT) {
+            Some((tx, files, flags)) => {
+                filetracker_newchunk(&mut tx.file_tracker, files, flags,
+                        &file_name, reply.data, chunk_offset,
+                        reply.count, fill_bytes as u8, reply.eof, &r.hdr.xid);
+                if is_last {
+                    tx.file_last_xid = r.hdr.xid;
+                    tx.response_status = reply.status;
+                    tx.is_last = true;
+                    tx.response_done = true;
+                }
+                true
+            },
+            None => { false },
+        };
+        if !found {
+            let (tx, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOCLIENT);
+            filetracker_newchunk(&mut tx.file_tracker, files, flags,
+                    &file_name, reply.data, chunk_offset,
+                    reply.count, fill_bytes as u8, reply.eof, &r.hdr.xid);
+            tx.procedure = NFSPROC3_READ;
+            tx.xid = r.hdr.xid;
+            tx.is_first = true;
+            if is_last {
+                tx.file_last_xid = r.hdr.xid;
+                tx.response_status = reply.status;
+                tx.is_last = true;
+                tx.response_done = true;
+            }
+        }
+
+        //if is_last {
+        //    self.tc_txs_updated = true;
+        //}
+        self.tc_chunk_xid = r.hdr.xid;
+        self.tc_chunk_left = reply.count as u32 - reply.data.len() as u32;
+
+        SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {}",
+                r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left);
+        0
+    }
+
+    fn process_partial_read_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &Nfs3ReplyRead<'b>) -> u32 {
+        SCLogDebug!("REPLY {} to procedure READ blob size {} / {}",
+                r.hdr.xid, r.prog_data.len(), reply.count);
+
+        return self.process_read_record(r, reply, None);
+    }
+
+    fn peek_reply_record(&mut self, r: &RpcPacketHeader) -> u32 {
+        let xidmap;
+        match self.requestmap.get(&r.xid) {
+            Some(p) => { xidmap = p; },
+            _ => { SCLogDebug!("REPLY: xid {} NOT FOUND", r.xid); return 0; },
+        }
+
+        xidmap.procedure
+    }
+
+    /// Parsing function, handling TCP chunks fragmentation
+    pub fn parse_tcp_data_ts<'b>(&mut self, i: &'b[u8]) -> u32 {
+        let mut v : Vec<u8>;
+        let mut status = 0;
+        SCLogDebug!("parse_tcp_data_ts ({})",i.len());
+        //SCLogDebug!("{:?}",i);
+        // Check if TCP data is being defragmented
+        let tcp_buffer = match self.tcp_buffer_ts.len() {
+            0 => i,
+            _ => {
+                v = self.tcp_buffer_ts.split_off(0);
+                // sanity check vector length to avoid memory exhaustion
+                if self.tcp_buffer_ts.len() + i.len() > 1000000 {
+                    SCLogNotice!("parse_tcp_data_ts: TS buffer exploded {} {}",
+                            self.tcp_buffer_ts.len(), i.len());
+                    return 1;
+                };
+                v.extend_from_slice(i);
+                v.as_slice()
+            },
+        };
+        //SCLogDebug!("tcp_buffer ({})",tcp_buffer.len());
+        let mut cur_i = tcp_buffer;
+        if cur_i.len() > 1000000 {
+            SCLogNotice!("BUG buffer exploded: {}", cur_i.len());
+        }
+
+        // take care of in progress file chunk transfers
+        // and skip buffer beyond it
+        let consumed = self.filetracker_update(STREAM_TOSERVER, cur_i);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 { panic!("BUG consumed more than we gave it"); }
+            cur_i = &cur_i[consumed as usize..];
+        }
+
+        while cur_i.len() > 0 { // min record size
+            match parse_rpc_request_partial(cur_i) {
+                IResult::Done(_, ref rpc_phdr) => {
+                    let rec_size = (rpc_phdr.hdr.frag_len + 4) as usize;
+                    //SCLogDebug!("rec_size {}/{}", rec_size, cur_i.len());
+                    //SCLogDebug!("cur_i {:?}", cur_i);
+
+                    if rec_size > 40000 { panic!("invalid rec_size"); }
+                    if rec_size > cur_i.len() {
+                        // special case: avoid buffering file write blobs
+                        // as these can be large.
+                        if rec_size >= 512 && cur_i.len() >= 44 {
+                            // large record, likely file xfer
+                            SCLogDebug!("large record {}, likely file xfer", rec_size);
+
+                            // quick peek, are in WRITE mode?
+                            if rpc_phdr.procedure == NFSPROC3_WRITE {
+                                SCLogDebug!("CONFIRMED WRITE: large record {}, file chunk xfer", rec_size);
+
+                                // lets try to parse the RPC record. Might fail with Incomplete.
+                                match parse_rpc(cur_i) {
+                                    IResult::Done(remaining, ref rpc_record) => {
+                                        match parse_nfs3_request_write(rpc_record.prog_data) {
+                                            IResult::Done(_, ref nfs_request_write) => {
+                                                // deal with the partial nfs write data
+                                                status |= self.process_partial_write_request_record(rpc_record, nfs_request_write);
+                                                cur_i = remaining; // progress input past parsed record
+                                            },
+                                            IResult::Incomplete(_) => {
+                                                SCLogDebug!("TS WRITE record incomplete");
+                                            },
+                                            IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+                                        }
+                                    },
+                                    IResult::Incomplete(_) => {
+                                        // we just size checked for the minimal record size above,
+                                        // so if options are used (creds/verifier), we can still
+                                        // have Incomplete data. Fall through to the buffer code
+                                        // and try again on our next iteration.
+                                        SCLogDebug!("TS data incomplete");
+                                    },
+                                    IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+                                }
+                            }
+                        }
+                        self.tcp_buffer_ts.extend_from_slice(cur_i);
+                        break;
+                    }
+
+                    // we have the full records size worth of data,
+                    // let's parse it
+                    match parse_rpc(&cur_i[..rec_size]) {
+                        IResult::Done(_, ref rpc_record) => {
+                            cur_i = &cur_i[rec_size..];
+                            status |= self.process_request_record(rpc_record);
+                        },
+                        IResult::Incomplete(x) => {
+                            // should be unreachable unless our rec_size calc is off
+                            panic!("TS data incomplete while we checked for rec_size? BUG {:?}", x);
+                            //self.tcp_buffer_ts.extend_from_slice(cur_i);
+                            //break;
+                        },
+                        IResult::Error(e) => { panic!("Parsing failed: {:?}",e); //break
+                        },
+                    }
+                },
+                IResult::Incomplete(_) => {
+                    SCLogDebug!("Fragmentation required (TCP level) 2");
+                    self.tcp_buffer_ts.extend_from_slice(cur_i);
+                    break;
+                },
+                IResult::Error(e) => { panic!("Parsing failed: {:?}",e); //break
+                },
+            }
+        };
+        status
+    }
+
+    /// Parsing function, handling TCP chunks fragmentation
+    pub fn parse_tcp_data_tc<'b>(&mut self, i: &'b[u8]) -> u32 {
+        let mut v : Vec<u8>;
+        let mut status = 0;
+        SCLogDebug!("parse_tcp_data_tc ({})",i.len());
+        //SCLogDebug!("{:?}",i);
+        // Check if TCP data is being defragmented
+        let tcp_buffer = match self.tcp_buffer_tc.len() {
+            0 => i,
+            _ => {
+                v = self.tcp_buffer_tc.split_off(0);
+                // sanity check vector length to avoid memory exhaustion
+                if self.tcp_buffer_tc.len() + i.len() > 100000 {
+                    SCLogDebug!("TC buffer exploded");
+                    return 1;
+                };
+                v.extend_from_slice(i);
+                v.as_slice()
+            },
+        };
+        SCLogDebug!("tcp_buffer ({})",tcp_buffer.len());
+
+        let mut cur_i = tcp_buffer;
+        if cur_i.len() > 100000 {
+            SCLogNotice!("parse_tcp_data_tc: BUG buffer exploded {}", cur_i.len());
+        }
+
+        // take care of in progress file chunk transfers
+        // and skip buffer beyond it
+        let consumed = self.filetracker_update(STREAM_TOCLIENT, cur_i);
+        if consumed > 0 {
+            if consumed > cur_i.len() as u32 { panic!("BUG consumed more than we gave it"); }
+            cur_i = &cur_i[consumed as usize..];
+        }
+
+        while cur_i.len() > 0 {
+            match parse_rpc_packet_header(cur_i) {
+                IResult::Done(_, ref rpc_hdr) => {
+                    let rec_size = (rpc_hdr.frag_len + 4) as usize;
+                    // see if we have all data available
+                    if rec_size > cur_i.len() {
+                        // special case: avoid buffering file read blobs
+                        // as these can be large.
+                        if rec_size >= 512 && cur_i.len() >= 128 {//36 {
+                            // large record, likely file xfer
+                            SCLogDebug!("large record {}, likely file xfer", rec_size);
+
+                            // quick peek, are in READ mode?
+                            if self.peek_reply_record(&rpc_hdr) == NFSPROC3_READ {
+                                SCLogDebug!("CONFIRMED large READ record {}, likely file chunk xfer", rec_size);
+
+                                // we should have enough data to parse the RPC record
+                                match parse_rpc_reply(cur_i) {
+                                    IResult::Done(remaining, ref rpc_record) => {
+                                        match parse_nfs3_reply_read(rpc_record.prog_data) {
+                                            IResult::Done(_, ref nfs_reply_read) => {
+                                                // deal with the partial nfs read data
+                                                status |= self.process_partial_read_reply_record(rpc_record, nfs_reply_read);
+                                                cur_i = remaining; // progress input past parsed record
+                                            },
+                                            IResult::Incomplete(_) => {
+                                                SCLogDebug!("TS WRITE record incomplete");
+                                            },
+                                            IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+                                        }
+                                    },
+                                    IResult::Incomplete(_) => {
+                                        // size check was done for MINIMAL record size,
+                                        // so Incomplete is normal.
+                                        SCLogDebug!("TC data incomplete");
+                                    },
+                                    IResult::Error(e) => { panic!("Parsing failed: {:?}",e); },
+                                }
+                            }
+                        }
+                        self.tcp_buffer_tc.extend_from_slice(cur_i);
+                        break;
+                    }
+
+                    // we have the full data of the record, lets parse
+                    match parse_rpc_reply(&cur_i[..rec_size]) {
+                        IResult::Done(_, ref rpc_record) => {
+                            cur_i = &cur_i[rec_size..]; // progress input past parsed record
+                            status |= self.process_reply_record(rpc_record);
+                        },
+                        IResult::Incomplete(_) => {
+                            // we shouldn't get incomplete as we have the full data
+                            panic!("TC data incomplete, BUG!");
+                            //self.tcp_buffer_tc.extend_from_slice(cur_i);
+                            //break;
+                        },
+                        IResult::Error(e) => { panic!("Parsing failed: {:?}",e); //break
+                        },
+                    }
+                },
+                IResult::Incomplete(_) => {
+                    SCLogDebug!("REPLY: insufficient data for HDR");
+                    self.tcp_buffer_tc.extend_from_slice(cur_i);
+                    break;
+                },
+                IResult::Error(e) => { SCLogDebug!("Parsing failed: {:?}",e); break },
+            }
+        };
+        status
+    }
+    fn getfiles(&mut self, direction: u8) -> * mut FileContainer {
+        //SCLogDebug!("direction: {}", direction);
+        if direction == STREAM_TOCLIENT {
+            &mut self.files.files_tc as *mut FileContainer
+        } else {
+            &mut self.files.files_ts as *mut FileContainer
+        }
+    }
+    fn setfileflags(&mut self, direction: u8, flags: u16) {
+        SCLogDebug!("direction: {}, flags: {}", direction, flags);
+        if direction == 1 {
+            self.files.flags_tc = flags;
+        } else {
+            self.files.flags_ts = flags;
+        }
+    }
+}
+
+/// Returns *mut NFS3State
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_new() -> *mut libc::c_void {
+    let state = NFS3State::new();
+    let boxed = Box::new(state);
+    SCLogDebug!("allocating state");
+    return unsafe{transmute(boxed)};
+}
+
+/// Params:
+/// - state: *mut NFS3State as void pointer
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_free(state: *mut libc::c_void) {
+    // Just unbox...
+    SCLogDebug!("freeing state");
+    let mut nfs3_state: Box<NFS3State> = unsafe{transmute(state)};
+    nfs3_state.free();
+}
+
+/// C binding parse a DNS request. Returns 1 on success, -1 on failure.
+#[no_mangle]
+pub extern "C" fn rs_nfs3_parse_request(_flow: *mut Flow,
+                                       state: &mut NFS3State,
+                                       _pstate: *mut libc::c_void,
+                                       input: *mut libc::uint8_t,
+                                       input_len: libc::uint32_t,
+                                       _data: *mut libc::c_void)
+                                       -> libc::int8_t
+{
+    let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
+    SCLogDebug!("parsing {} bytes of request data", input_len);
+    if state.parse_tcp_data_ts(buf) == 0 {
+        1
+    } else {
+        -1
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_parse_response(_flow: *mut Flow,
+                                        state: &mut NFS3State,
+                                        _pstate: *mut libc::c_void,
+                                        input: *mut libc::uint8_t,
+                                        input_len: libc::uint32_t,
+                                        _data: *mut libc::c_void)
+                                        -> libc::int8_t
+{
+    SCLogDebug!("parsing {} bytes of response data", input_len);
+    let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)};
+    if state.parse_tcp_data_tc(buf) == 0 {
+        1
+    } else {
+        -1
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_get_tx_count(state: &mut NFS3State)
+                                            -> libc::uint64_t
+{
+    SCLogDebug!("rs_nfs3_state_get_tx_count: returning {}", state.tx_id);
+    return state.tx_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_get_tx(state: &mut NFS3State,
+                                      tx_id: libc::uint64_t)
+                                      -> *mut NFS3Transaction
+{
+    match state.get_tx_by_id(tx_id) {
+        Some(tx) => {
+            return unsafe{transmute(tx)};
+        }
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_tx_free(state: &mut NFS3State,
+                                       tx_id: libc::uint64_t)
+{
+    state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_progress_completion_status(
+    _direction: libc::uint8_t)
+    -> libc::c_int
+{
+    return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_tx_get_alstate_progress(tx: &mut NFS3Transaction,
+                                                  direction: libc::uint8_t)
+                                                  -> libc::uint8_t
+{
+    if direction == STREAM_TOSERVER && tx.request_done {
+        //SCLogNotice!("TOSERVER progress 1");
+        return 1;
+    } else if direction == STREAM_TOCLIENT && tx.response_done {
+        //SCLogNotice!("TOCLIENT progress 1");
+        return 1;
+    } else {
+        //SCLogNotice!("{} progress 0", direction);
+        return 0;
+    }
+}
+
+/*
+#[no_mangle]
+pub extern "C" fn rs_nfs3_get_txs_updated(state: &mut NFS3State,
+                                          direction: u8) -> bool
+{
+    if direction == STREAM_TOSERVER {
+        return state.ts_txs_updated;
+    } else {
+        return state.tc_txs_updated;
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_reset_txs_updated(state: &mut NFS3State,
+                                            direction: u8)
+{
+    if direction == STREAM_TOSERVER {
+        state.ts_txs_updated = false;
+    } else {
+        state.tc_txs_updated = false;
+    }
+}
+*/
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_tx_set_logged(_state: &mut NFS3State,
+                                       tx: &mut NFS3Transaction,
+                                       logger: libc::uint32_t)
+{
+    tx.logged.set_logged(logger);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_tx_get_logged(_state: &mut NFS3State,
+                                       tx: &mut NFS3Transaction,
+                                       logger: libc::uint32_t)
+                                       -> i8
+{
+    if tx.logged.is_logged(logger) {
+        return 1;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_has_detect_state(state: &mut NFS3State) -> u8
+{
+    if state.de_state_count > 0 {
+        return 1;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_set_tx_detect_state(
+    state: &mut NFS3State,
+    tx: &mut NFS3Transaction,
+    de_state: &mut DetectEngineState)
+{
+    state.de_state_count += 1;
+    tx.de_state = Some(de_state);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_state_get_tx_detect_state(
+    tx: &mut NFS3Transaction)
+    -> *mut DetectEngineState
+{
+    match tx.de_state {
+        Some(ds) => {
+            return ds;
+        },
+        None => {
+            return std::ptr::null_mut();
+        }
+    }
+}
+
+/// return procedure(s) in the tx. At 0 return the main proc,
+/// otherwise get procs from the 'file_additional_procs'.
+/// Keep calling until 0 is returned.
+#[no_mangle]
+pub extern "C" fn rs_nfs3_tx_get_procedures(tx: &mut NFS3Transaction,
+                                       i: libc::uint16_t,
+                                       procedure: *mut libc::uint32_t)
+                                       -> libc::uint8_t
+{
+    if i == 0 {
+        unsafe {
+            *procedure = tx.procedure as libc::uint32_t;
+        }
+        return 1;
+    }
+
+    let idx = i as usize - 1;
+    if idx < tx.file_additional_procs.len() {
+        let p = tx.file_additional_procs[idx];
+        unsafe {
+            *procedure = p as libc::uint32_t;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_init(context: &'static mut SuricataFileContext)
+{
+    unsafe {
+        suricata_nfs3_file_config = Some(context);
+    }
+}
+
+/// TOSERVER probe function
+#[no_mangle]
+pub extern "C" fn rs_nfs_probe(input: *const libc::uint8_t, len: libc::uint32_t)
+                               -> libc::int8_t
+{
+    let slice: &[u8] = unsafe {
+        std::slice::from_raw_parts(input as *mut u8, len as usize)
+    };
+    match parse_rpc(slice) {
+        IResult::Done(_, ref rpc_hdr) => {
+            if rpc_hdr.progver == 3 && rpc_hdr.program == 100003 {
+                return 1;
+            } else {
+                return -1;
+            }
+        },
+        IResult::Incomplete(_) => {
+            return 0;
+        },
+        IResult::Error(_) => {
+            return -1;
+        },
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs3_getfiles(direction: u8, ptr: *mut NFS3State) -> * mut FileContainer {
+    if ptr.is_null() { panic!("NULL ptr"); };
+    let parser = unsafe { &mut *ptr };
+    parser.getfiles(direction)
+}
+#[no_mangle]
+pub extern "C" fn rs_nfs3_setfileflags(direction: u8, ptr: *mut NFS3State, flags: u16) {
+    if ptr.is_null() { panic!("NULL ptr"); };
+    let parser = unsafe { &mut *ptr };
+    SCLogDebug!("direction {} flags {}", direction, flags);
+    parser.setfileflags(direction, flags)
+}
+
diff --git a/rust/src/nfs/parser.rs b/rust/src/nfs/parser.rs
new file mode 100644 (file)
index 0000000..4f67cd0
--- /dev/null
@@ -0,0 +1,531 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+//! Nom parsers for RPC & NFSv3
+
+use nom::{be_u32, be_u64, rest};
+
+#[derive(Debug,PartialEq)]
+pub struct RpcRequestCredsUnix<'a> {
+    pub stamp: u32,
+    pub machine_name_len: u32,
+    pub machine_name_buf: &'a[u8],
+    pub uid: u32,
+    pub gid: u32,
+    pub aux_gids: Option<Vec<u32>>,
+    // list of gids
+}
+
+//named!(parse_rpc_creds_unix_aux_gids<Vec<u32>>,
+//    many0!(be_u32)
+//);
+
+named!(pub parse_rfc_request_creds_unix<RpcRequestCredsUnix>,
+    do_parse!(
+           stamp: be_u32
+        >> machine_name_len: be_u32
+        >> machine_name_buf: take!(machine_name_len)
+        >> uid: be_u32
+        >> gid: be_u32
+        //>> aux_gids: parse_rpc_creds_unix_aux_gids
+
+        >> (
+            RpcRequestCredsUnix {
+                stamp:stamp,
+                machine_name_len:machine_name_len,
+                machine_name_buf:machine_name_buf,
+                uid:uid,
+                gid:gid,
+                aux_gids:None,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3Handle<'a> {
+    pub len: u32,
+    pub value: &'a[u8],
+}
+
+named!(pub parse_nfs3_handle<Nfs3Handle>,
+    do_parse!(
+        obj_len: be_u32
+        >> obj: take!(obj_len)
+        >> (
+            Nfs3Handle {
+                len:obj_len,
+                value:obj,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ReplyCreate<'a> {
+    pub status: u32,
+    pub handle: Option<Nfs3Handle<'a>>,
+}
+
+named!(pub parse_nfs3_response_create<Nfs3ReplyCreate>,
+    do_parse!(
+        status: be_u32
+        >> handle_has_value: be_u32
+        >> handle: cond!(handle_has_value == 1, parse_nfs3_handle)
+        >> (
+            Nfs3ReplyCreate {
+               status:status,
+               handle:handle,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ReplyLookup<'a> {
+    pub status: u32,
+    pub handle: Nfs3Handle<'a>,
+}
+
+named!(pub parse_nfs3_response_lookup<Nfs3ReplyLookup>,
+    do_parse!(
+        status: be_u32
+        >> handle: parse_nfs3_handle
+        >> (
+            Nfs3ReplyLookup {
+                status:status,
+                handle:handle,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestCreate<'a> {
+    pub handle: Nfs3Handle<'a>,
+    pub name_len: u32,
+    pub create_mode: u32,
+    pub verifier: &'a[u8],
+    pub name_vec: Vec<u8>,
+}
+
+named!(pub parse_nfs3_request_create<Nfs3RequestCreate>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  name_len: be_u32
+        >>  name: take!(name_len)
+        >>  create_mode: be_u32
+        >>  verifier: rest
+        >> (
+            Nfs3RequestCreate {
+                handle:handle,
+                name_len:name_len,
+                create_mode:create_mode,
+                verifier:verifier,
+                name_vec:name.to_vec(),
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestAccess<'a> {
+    pub handle: Nfs3Handle<'a>,
+    pub check_access: u32,
+}
+
+named!(pub parse_nfs3_request_access<Nfs3RequestAccess>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  check_access: be_u32
+        >> (
+            Nfs3RequestAccess {
+                handle:handle,
+                check_access:check_access,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestCommit<'a> {
+    pub handle: Nfs3Handle<'a>,
+}
+
+named!(pub parse_nfs3_request_commit<Nfs3RequestCommit>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  offset: be_u64
+        >>  count: be_u32
+        >> (
+            Nfs3RequestCommit {
+                handle:handle,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestRead<'a> {
+    pub handle: Nfs3Handle<'a>,
+    pub offset: u64,
+}
+
+named!(pub parse_nfs3_request_read<Nfs3RequestRead>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  offset: be_u64
+        >>  count: be_u32
+        >> (
+            Nfs3RequestRead {
+                handle:handle,
+                offset:offset,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestLookup<'a> {
+    pub handle: Nfs3Handle<'a>,
+
+    pub name_vec: Vec<u8>,
+}
+
+named!(pub parse_nfs3_request_lookup<Nfs3RequestLookup>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  name_len: be_u32
+        >>  name_contents: take!(name_len)
+        >>  name_padding: rest
+        >> (
+            Nfs3RequestLookup {
+                handle:handle,
+                name_vec:name_contents.to_vec(),
+            }
+        ))
+);
+
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ResponseReaddirplusEntryC<'a> {
+    pub name_vec: Vec<u8>,
+    pub handle: Option<Nfs3Handle<'a>>,
+}
+
+named!(pub parse_nfs3_response_readdirplus_entry<Nfs3ResponseReaddirplusEntryC>,
+    do_parse!(
+        file_id: be_u64
+        >> name_len: be_u32
+        >> name_content: take!(name_len)
+        >> fill_bytes: cond!(name_len % 4 != 0, take!(4 - name_len % 4))
+        >> cookie: take!(8)
+        >> attr_value_follows: be_u32
+        >> attr: cond!(attr_value_follows==1, take!(84))
+        >> handle_value_follows: be_u32
+        >> handle: cond!(handle_value_follows==1, parse_nfs3_handle)
+        >> (
+                Nfs3ResponseReaddirplusEntryC {
+                    name_vec:name_content.to_vec(),
+                    handle:handle,
+                }
+           )
+        )
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ResponseReaddirplusEntry<'a> {
+    pub entry: Option<Nfs3ResponseReaddirplusEntryC<'a>>,
+}
+
+named!(pub parse_nfs3_response_readdirplus_entry_cond<Nfs3ResponseReaddirplusEntry>,
+    do_parse!(
+        value_follows: be_u32
+        >> entry: cond!(value_follows==1, parse_nfs3_response_readdirplus_entry)
+        >> (
+            Nfs3ResponseReaddirplusEntry {
+                entry:entry,
+            }
+           ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ResponseReaddirplus<'a> {
+    pub status: u32,
+    pub data: &'a[u8],
+}
+
+named!(pub parse_nfs3_response_readdirplus<Nfs3ResponseReaddirplus>,
+    do_parse!(
+        status: be_u32
+        >> dir_attr_follows: be_u32
+        >> dir_attr: cond!(dir_attr_follows == 1, take!(84))
+        >> verifier: take!(8)
+        >> data: rest
+
+        >> ( Nfs3ResponseReaddirplus {
+                status:status,
+                data:data,
+        } ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestReaddirplus<'a> {
+    pub handle: Nfs3Handle<'a>,
+
+    pub cookie: u32,
+    pub verifier: &'a[u8],
+    pub dircount: u32,
+    pub maxcount: u32,
+}
+
+named!(pub parse_nfs3_request_readdirplus<Nfs3RequestReaddirplus>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  cookie: be_u32
+        >>  verifier: take!(8)
+        >>  dircount: be_u32
+        >>  maxcount: be_u32
+        >> (
+            Nfs3RequestReaddirplus {
+                handle:handle,
+                cookie:cookie,
+                verifier:verifier,
+                dircount:dircount,
+                maxcount:maxcount,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3RequestWrite<'a> {
+    pub handle: Nfs3Handle<'a>,
+
+    pub offset: u64,
+    pub count: u32,
+    pub stable: u32,
+    pub file_len: u32,
+    pub file_data: &'a[u8],
+}
+
+named!(pub parse_nfs3_request_write<Nfs3RequestWrite>,
+    do_parse!(
+            handle: parse_nfs3_handle
+        >>  offset: be_u64
+        >>  count: be_u32
+        >>  stable: be_u32
+        >>  file_len: be_u32
+        >>  file_data: rest // likely partial
+        >> (
+            Nfs3RequestWrite {
+                handle:handle,
+                offset:offset,
+                count:count,
+                stable:stable,
+                file_len:file_len,
+                file_data:file_data,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs3ReplyRead<'a> {
+    pub status: u32,
+    pub attr_follows: u32,
+    pub attr_blob: &'a[u8],
+    pub count: u32,
+    pub eof: bool,
+    pub data_len: u32,
+    pub data: &'a[u8], // likely partial
+}
+
+named!(pub parse_nfs3_reply_read<Nfs3ReplyRead>,
+    do_parse!(
+            status: be_u32
+        >>  attr_follows: be_u32
+        >>  attr_blob: take!(84) // fixed size?
+        >>  count: be_u32
+        >>  eof: be_u32
+        >>  data_len: be_u32
+        >>  data_contents: rest
+        >> (
+            Nfs3ReplyRead {
+                status:status,
+                attr_follows:attr_follows,
+                attr_blob:attr_blob,
+                count:count,
+                eof:eof != 0,
+                data_len:data_len,
+                data:data_contents,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct RpcPacketHeader<> {
+    pub frag_is_last: bool,
+    pub frag_len: u32,
+    pub xid: u32,
+    pub msgtype: u32,
+}
+
+named!(pub parse_rpc_packet_header<RpcPacketHeader>,
+    do_parse!(
+        fraghdr: bits!(tuple!(
+                take_bits!(u8, 1),       // is_last
+                take_bits!(u32, 31)))    // len
+
+        >> xid: be_u32
+        >> msgtype: be_u32
+        >> (
+            RpcPacketHeader {
+                frag_is_last:fraghdr.0 == 1,
+                frag_len:fraghdr.1,
+                xid:xid,
+                msgtype:msgtype,
+            }
+        ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct RpcReplyPacket<'a> {
+    pub hdr: RpcPacketHeader<>,
+
+    pub verifier_flavor: u32,
+    pub verifier_len: u32,
+    pub verifier: Option<&'a[u8]>,
+
+    pub reply_state: u32,
+    pub accept_state: u32,
+
+    pub prog_data: &'a[u8],
+}
+
+// top of request packet, just to get to procedure
+#[derive(Debug)]
+pub struct RpcRequestPacketPartial {
+    pub hdr: RpcPacketHeader,
+
+    pub rpcver: u32,
+    pub program: u32,
+    pub progver: u32,
+    pub procedure: u32,
+}
+
+named!(pub parse_rpc_request_partial<RpcRequestPacketPartial>,
+   do_parse!(
+       hdr: parse_rpc_packet_header
+       >> rpcver: be_u32
+       >> program: be_u32
+       >> progver: be_u32
+       >> procedure: be_u32
+       >> (
+            RpcRequestPacketPartial {
+                hdr:hdr,
+                rpcver:rpcver,
+                program:program,
+                progver:progver,
+                procedure:procedure,
+            }
+          ))
+);
+
+#[derive(Debug,PartialEq)]
+pub struct RpcPacket<'a> {
+    pub hdr: RpcPacketHeader<>,
+
+    pub rpcver: u32,
+    pub program: u32,
+    pub progver: u32,
+    pub procedure: u32,
+
+    pub creds_flavor: u32,
+    pub creds_len: u32,
+    pub creds: Option<&'a[u8]>,
+    pub creds_unix:Option<RpcRequestCredsUnix<'a>>,
+
+    pub verifier_flavor: u32,
+    pub verifier_len: u32,
+    pub verifier: Option<&'a[u8]>,
+
+    pub prog_data: &'a[u8],
+}
+
+named!(pub parse_rpc<RpcPacket>,
+   do_parse!(
+       hdr: parse_rpc_packet_header
+
+       >> rpcver: be_u32
+       >> program: be_u32
+       >> progver: be_u32
+       >> procedure: be_u32
+
+       >> creds_flavor: be_u32
+       >> creds_len: be_u32
+       >> creds: cond!(creds_flavor != 1 && creds_len > 0, take!(creds_len as usize))
+       >> creds_unix: cond!(creds_len > 0 && creds_flavor == 1, flat_map!(take!((creds_len) as usize),parse_rfc_request_creds_unix))
+
+       >> verifier_flavor: be_u32
+       >> verifier_len: be_u32
+       >> verifier: cond!(verifier_len > 0, take!(verifier_len as usize))
+
+       >> pl: rest
+
+       >> (
+           RpcPacket {
+                hdr:hdr,
+
+                rpcver:rpcver,
+                program:program,
+                progver:progver,
+                procedure:procedure,
+
+                creds_flavor:creds_flavor,
+                creds_len:creds_len,
+                creds:creds,
+                creds_unix:creds_unix,
+
+                verifier_flavor:verifier_flavor,
+                verifier_len:verifier_len,
+                verifier:verifier,
+
+                prog_data:pl,
+           }
+   ))
+);
+
+// to be called with data <= hdr.frag_len + 4. Sending more data is undefined.
+named!(pub parse_rpc_reply<RpcReplyPacket>,
+   do_parse!(
+       hdr: parse_rpc_packet_header
+
+       >> verifier_flavor: be_u32
+       >> verifier_len: be_u32
+       >> verifier: cond!(verifier_len > 0, take!(verifier_len as usize))
+
+       >> reply_state: be_u32
+       >> accept_state: be_u32
+
+       >> pl: rest
+
+       >> (
+           RpcReplyPacket {
+                hdr:hdr,
+
+                verifier_flavor:verifier_flavor,
+                verifier_len:verifier_len,
+                verifier:verifier,
+
+                reply_state:reply_state,
+                accept_state:accept_state,
+
+                prog_data:pl,
+           }
+   ))
+);
diff --git a/rust/src/nfs/types.rs b/rust/src/nfs/types.rs
new file mode 100644 (file)
index 0000000..9e8c00e
--- /dev/null
@@ -0,0 +1,138 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/* RFC 1813, section '3. Server Procedures' */
+pub const NFSPROC3_NULL:        u32 = 0;
+pub const NFSPROC3_GETATTR:     u32 = 1;
+pub const NFSPROC3_SETATTR:     u32 = 2;
+pub const NFSPROC3_LOOKUP:      u32 = 3;
+pub const NFSPROC3_ACCESS:      u32 = 4;
+pub const NFSPROC3_READLINK:    u32 = 5;
+pub const NFSPROC3_READ:        u32 = 6;
+pub const NFSPROC3_WRITE:       u32 = 7;
+pub const NFSPROC3_CREATE:      u32 = 8;
+pub const NFSPROC3_MKDIR:       u32 = 9;
+pub const NFSPROC3_SYMLINK:     u32 = 10;
+pub const NFSPROC3_MKNOD:       u32 = 11;
+pub const NFSPROC3_REMOVE:      u32 = 12;
+pub const NFSPROC3_RMDIR:       u32 = 13;
+pub const NFSPROC3_RENAME:      u32 = 14;
+pub const NFSPROC3_LINK:        u32 = 15;
+pub const NFSPROC3_READDIR:     u32 = 16;
+pub const NFSPROC3_READDIRPLUS: u32 = 17;
+pub const NFSPROC3_FSSTAT:      u32 = 18;
+pub const NFSPROC3_FSINFO:      u32 = 19;
+pub const NFSPROC3_PATHCONF:    u32 = 20;
+pub const NFSPROC3_COMMIT:      u32 = 21;
+
+pub fn nfs3_procedure_string(procedure: u32) -> String {
+    match procedure {
+        NFSPROC3_NULL           => "NULL",
+        NFSPROC3_GETATTR        => "GETATTR",
+        NFSPROC3_SETATTR        => "SETATTR",
+        NFSPROC3_LOOKUP         => "LOOKUP",
+        NFSPROC3_ACCESS         => "ACCESS",
+        NFSPROC3_READLINK       => "READLINK",
+        NFSPROC3_READ           => "READ",
+        NFSPROC3_WRITE          => "WRITE",
+        NFSPROC3_CREATE         => "CREATE",
+        NFSPROC3_MKDIR          => "MKDIR",
+        NFSPROC3_SYMLINK        => "SYMLINK",
+        NFSPROC3_MKNOD          => "MKNOD",
+        NFSPROC3_REMOVE         => "REMOVE",
+        NFSPROC3_RMDIR          => "RMDIR",
+        NFSPROC3_RENAME         => "RENAME",
+        NFSPROC3_LINK           => "LINK",
+        NFSPROC3_READDIR        => "READDIR",
+        NFSPROC3_READDIRPLUS    => "READDIRPLUS",
+        NFSPROC3_FSSTAT         => "FSSTAT",
+        NFSPROC3_FSINFO         => "FSINFO",
+        NFSPROC3_PATHCONF       => "PATHCONF",
+        NFSPROC3_COMMIT         => "COMMIT",
+        _ => {
+            return (procedure).to_string();
+        }
+    }.to_string()
+}
+
+/* RFC 1813, section '2.6 Defined Error Numbers' */
+pub const NFS3_OK:              u32 = 0;
+pub const NFS3ERR_PERM:         u32 = 1;
+pub const NFS3ERR_NOENT:        u32 = 2;
+pub const NFS3ERR_IO:           u32 = 5;
+pub const NFS3ERR_NXIO:         u32 = 6;
+pub const NFS3ERR_ACCES:        u32 = 13;
+pub const NFS3ERR_EXIST:        u32 = 17;
+pub const NFS3ERR_XDEV:         u32 = 18;
+pub const NFS3ERR_NODEV:        u32 = 19;
+pub const NFS3ERR_NOTDIR:       u32 = 20;
+pub const NFS3ERR_ISDIR:        u32 = 21;
+pub const NFS3ERR_INVAL:        u32 = 22;
+pub const NFS3ERR_FBIG:         u32 = 27;
+pub const NFS3ERR_NOSPC:        u32 = 28;
+pub const NFS3ERR_ROFS:         u32 = 30;
+pub const NFS3ERR_MLINK:        u32 = 31;
+pub const NFS3ERR_NAMETOOLONG:  u32 = 63;
+pub const NFS3ERR_NOTEMPTY:     u32 = 66;
+pub const NFS3ERR_DQUOT:        u32 = 69;
+pub const NFS3ERR_STALE:        u32 = 70;
+pub const NFS3ERR_REMOTE:       u32 = 71;
+pub const NFS3ERR_BADHANDLE:    u32 = 10001;
+pub const NFS3ERR_NOT_SYNC:     u32 = 10002;
+pub const NFS3ERR_BAD_COOKIE:   u32 = 10003;
+pub const NFS3ERR_NOTSUPP:      u32 = 10004;
+pub const NFS3ERR_TOOSMALL:     u32 = 10005;
+pub const NFS3ERR_SERVERFAULT:  u32 = 10006;
+pub const NFS3ERR_BADTYPE:      u32 = 10007;
+pub const NFS3ERR_JUKEBOX:      u32 = 10008;
+
+pub fn nfs3_status_string(status: u32) -> String {
+    match status {
+        NFS3_OK             => "OK",
+        NFS3ERR_PERM        => "ERR_PERM",
+        NFS3ERR_NOENT       => "ERR_NOENT",
+        NFS3ERR_IO          => "ERR_IO",
+        NFS3ERR_NXIO        => "ERR_NXIO",
+        NFS3ERR_ACCES       => "ERR_ACCES",
+        NFS3ERR_EXIST       => "ERR_EXIST",
+        NFS3ERR_XDEV        => "ERR_XDEV",
+        NFS3ERR_NODEV       => "ERR_NODEV",
+        NFS3ERR_NOTDIR      => "ERR_NOTDIR",
+        NFS3ERR_ISDIR       => "ERR_ISDIR",
+        NFS3ERR_INVAL       => "ERR_INVAL",
+        NFS3ERR_FBIG        => "ERR_FBIG",
+        NFS3ERR_NOSPC       => "ERR_NOSPC",
+        NFS3ERR_ROFS        => "ERR_ROFS",
+        NFS3ERR_MLINK       => "ERR_MLINK",
+        NFS3ERR_NAMETOOLONG => "ERR_NAMETOOLONG",
+        NFS3ERR_NOTEMPTY    => "ERR_NOTEMPTY",
+        NFS3ERR_DQUOT       => "ERR_DQUOT",
+        NFS3ERR_STALE       => "ERR_STALE",
+        NFS3ERR_REMOTE      => "ERR_REMOTE",
+        NFS3ERR_BADHANDLE   => "ERR_BADHANDLE",
+        NFS3ERR_NOT_SYNC    => "ERR_NOT_SYNC",
+        NFS3ERR_BAD_COOKIE  => "ERR_BAD_COOKIE",
+        NFS3ERR_NOTSUPP     => "ERR_NOTSUPP",
+        NFS3ERR_TOOSMALL    => "ERR_TOOSMALL",
+        NFS3ERR_SERVERFAULT => "ERR_SERVERFAULT",
+        NFS3ERR_BADTYPE     => "ERR_BADTYPE",
+        NFS3ERR_JUKEBOX     => "ERR_JUKEBOX",
+        _ => {
+            return (status).to_string();
+        },
+    }.to_string()
+}
index a8bc12607f2bc16ccc7e9c2b2437251bf590310e..b1d789f2e52e2e2b9261fb350cb3bb7a3fe8eb18 100644 (file)
@@ -39,6 +39,7 @@ app-layer-protos.c app-layer-protos.h \
 app-layer-smb2.c app-layer-smb2.h \
 app-layer-smb.c app-layer-smb.h \
 app-layer-smtp.c app-layer-smtp.h \
+app-layer-nfs3.c app-layer-nfs3.h \
 app-layer-template.c app-layer-template.h \
 app-layer-ssh.c app-layer-ssh.h \
 app-layer-ssl.c app-layer-ssl.h \
@@ -207,6 +208,7 @@ detect-lua-extensions.c detect-lua-extensions.h \
 detect-mark.c detect-mark.h \
 detect-metadata.c detect-metadata.h \
 detect-msg.c detect-msg.h \
+detect-nfs3-procedure.c detect-nfs3-procedure.h \
 detect-noalert.c detect-noalert.h \
 detect-nocase.c detect-nocase.h \
 detect-offset.c detect-offset.h \
@@ -296,6 +298,7 @@ output-json-smtp.c output-json-smtp.h \
 output-json-ssh.c output-json-ssh.h \
 output-json-stats.c output-json-stats.h \
 output-json-tls.c output-json-tls.h \
+output-json-nfs3.c output-json-nfs3.h \
 output-json-template.c output-json-template.h \
 output-json-vars.c output-json-vars.h \
 output-lua.c output-lua.h \
index 8eb92639f7d419dfb046eaca23a8c9ccc9a3b82f..05c11f368f75b25975c337e784010968df0bfa5b 100644 (file)
@@ -696,6 +696,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                         printf("            alproto: ALPROTO_MODBUS\n");
                     else if (pp_pe->alproto == ALPROTO_ENIP)
                         printf("            alproto: ALPROTO_ENIP\n");
+                    else if (pp_pe->alproto == ALPROTO_NFS3)
+                        printf("            alproto: ALPROTO_NFS3\n");
                     else if (pp_pe->alproto == ALPROTO_TEMPLATE)
                         printf("            alproto: ALPROTO_TEMPLATE\n");
                     else if (pp_pe->alproto == ALPROTO_DNP3)
@@ -753,6 +755,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar
                     printf("            alproto: ALPROTO_MODBUS\n");
                 else if (pp_pe->alproto == ALPROTO_ENIP)
                     printf("            alproto: ALPROTO_ENIP\n");
+                else if (pp_pe->alproto == ALPROTO_NFS3)
+                    printf("            alproto: ALPROTO_NFS3\n");
                 else if (pp_pe->alproto == ALPROTO_TEMPLATE)
                     printf("            alproto: ALPROTO_TEMPLATE\n");
                 else if (pp_pe->alproto == ALPROTO_DNP3)
diff --git a/src/app-layer-nfs3.c b/src/app-layer-nfs3.c
new file mode 100644 (file)
index 0000000..7cdc314
--- /dev/null
@@ -0,0 +1,369 @@
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ *
+ * NFS3 application layer detector and parser for learning and
+ * nfs3 pruposes.
+ *
+ * This nfs3 implements a simple application layer for something
+ * like the NFS3 protocol running on port 2049.
+ */
+
+#include "suricata-common.h"
+#include "stream.h"
+#include "conf.h"
+
+#include "util-unittest.h"
+
+#include "app-layer-detect-proto.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-nfs3.h"
+
+#ifndef HAVE_RUST
+void RegisterNFS3Parsers(void)
+{
+}
+
+#else
+
+#include "rust.h"
+#include "rust-nfs-nfs3-gen.h"
+
+/* The default port to probe for echo traffic if not provided in the
+ * configuration file. */
+#define NFS3_DEFAULT_PORT "2049"
+
+/* The minimum size for a RFC message. For some protocols this might
+ * be the size of a header. TODO actual min size is likely larger */
+#define NFS3_MIN_FRAME_LEN 32
+
+/* Enum of app-layer events for an echo protocol. Normally you might
+ * have events for errors in parsing data, like unexpected data being
+ * received. For echo we'll make something up, and log an app-layer
+ * level alert if an empty message is received.
+ *
+ * Example rule:
+ *
+ * alert nfs3 any any -> any any (msg:"SURICATA NFS3 empty message"; \
+ *    app-layer-event:nfs3.empty_message; sid:X; rev:Y;)
+ */
+enum {
+    NFS3_DECODER_EVENT_EMPTY_MESSAGE,
+};
+
+SCEnumCharMap nfs3_decoder_event_table[] = {
+    {"EMPTY_MESSAGE", NFS3_DECODER_EVENT_EMPTY_MESSAGE},
+    { NULL, 0 }
+};
+
+static void *NFS3StateAlloc(void)
+{
+    return rs_nfs3_state_new();
+}
+
+static void NFS3StateFree(void *state)
+{
+    rs_nfs3_state_free(state);
+}
+
+/**
+ * \brief Callback from the application layer to have a transaction freed.
+ *
+ * \param state a void pointer to the NFS3State object.
+ * \param tx_id the transaction ID to free.
+ */
+static void NFS3StateTxFree(void *state, uint64_t tx_id)
+{
+    rs_nfs3_state_tx_free(state, tx_id);
+}
+
+#if 0
+static int NFS3StateGetEventInfo(const char *event_name, int *event_id,
+    AppLayerEventType *event_type)
+{
+    *event_id = SCMapEnumNameToValue(event_name, nfs3_decoder_event_table);
+    if (*event_id == -1) {
+        SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
+                   "nfs3 enum map table.",  event_name);
+        /* This should be treated as fatal. */
+        return -1;
+    }
+
+    *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
+
+    return 0;
+}
+
+static AppLayerDecoderEvents *NFS3GetEvents(void *state, uint64_t tx_id)
+{
+    NFS3State *nfs3_state = state;
+    NFS3Transaction *tx;
+
+    TAILQ_FOREACH(tx, &nfs3_state->tx_list, next) {
+        if (tx->tx_id == tx_id) {
+            return tx->decoder_events;
+        }
+    }
+
+    return NULL;
+}
+
+static int NFS3HasEvents(void *state)
+{
+    NFS3State *echo = state;
+    return echo->events;
+}
+#endif
+
+/**
+ * \brief Probe the input to see if it looks like echo.
+ *
+ * \retval ALPROTO_NFS3 if it looks like echo, otherwise
+ *     ALPROTO_UNKNOWN.
+ */
+static AppProto NFS3ProbingParser(uint8_t *input, uint32_t input_len,
+    uint32_t *offset)
+{
+    if (input_len < NFS3_MIN_FRAME_LEN) {
+        return ALPROTO_UNKNOWN;
+    }
+
+    int8_t r = rs_nfs_probe(input, input_len);
+    if (r == 1) {
+        return ALPROTO_NFS3;
+    } else if (r == -1) {
+        return ALPROTO_FAILED;
+    }
+
+    SCLogDebug("Protocol not detected as ALPROTO_NFS3.");
+    return ALPROTO_UNKNOWN;
+}
+
+static int NFS3ParseRequest(Flow *f, void *state,
+    AppLayerParserState *pstate, uint8_t *input, uint32_t input_len,
+    void *local_data)
+{
+    uint16_t file_flags = FileFlowToFlags(f, STREAM_TOSERVER);
+    rs_nfs3_setfileflags(0, state, file_flags);
+
+    return rs_nfs3_parse_request(f, state, pstate, input, input_len, local_data);
+}
+
+static int NFS3ParseResponse(Flow *f, void *state, AppLayerParserState *pstate,
+    uint8_t *input, uint32_t input_len, void *local_data)
+{
+    uint16_t file_flags = FileFlowToFlags(f, STREAM_TOCLIENT);
+    rs_nfs3_setfileflags(1, state, file_flags);
+
+    return rs_nfs3_parse_response(f, state, pstate, input, input_len, local_data);
+}
+
+static uint64_t NFS3GetTxCnt(void *state)
+{
+    return rs_nfs3_state_get_tx_count(state);
+}
+
+static void *NFS3GetTx(void *state, uint64_t tx_id)
+{
+    return rs_nfs3_state_get_tx(state, tx_id);
+}
+
+static void NFS3SetTxLogged(void *state, void *vtx, uint32_t logger)
+{
+    rs_nfs3_tx_set_logged(state, vtx, logger);
+}
+
+static int NFS3GetTxLogged(void *state, void *vtx, uint32_t logger)
+{
+    return rs_nfs3_tx_get_logged(state, vtx, logger);
+}
+
+/**
+ * \brief Called by the application layer.
+ *
+ * In most cases 1 can be returned here.
+ */
+static int NFS3GetAlstateProgressCompletionStatus(uint8_t direction) {
+    return rs_nfs3_state_progress_completion_status(direction);
+}
+
+/**
+ * \brief Return the state of a transaction in a given direction.
+ *
+ * In the case of the echo protocol, the existence of a transaction
+ * means that the request is done. However, some protocols that may
+ * need multiple chunks of data to complete the request may need more
+ * than just the existence of a transaction for the request to be
+ * considered complete.
+ *
+ * For the response to be considered done, the response for a request
+ * needs to be seen.  The response_done flag is set on response for
+ * checking here.
+ */
+static int NFS3GetStateProgress(void *tx, uint8_t direction)
+{
+    return rs_nfs3_tx_get_alstate_progress(tx, direction);
+}
+
+/**
+ * \brief get stored tx detect state
+ */
+static DetectEngineState *NFS3GetTxDetectState(void *vtx)
+{
+    return rs_nfs3_state_get_tx_detect_state(vtx);
+}
+
+/**
+ * \brief set store tx detect state
+ */
+static int NFS3SetTxDetectState(void *state, void *vtx,
+    DetectEngineState *s)
+{
+    rs_nfs3_state_set_tx_detect_state(state, vtx, s);
+    return 0;
+}
+
+static FileContainer *NFS3GetFiles(void *state, uint8_t direction)
+{
+    return rs_nfs3_getfiles(direction, state);
+}
+
+static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER;
+static SuricataFileContext sfc = { &sbcfg };
+
+void RegisterNFS3Parsers(void)
+{
+    const char *proto_name = "nfs3";
+
+    /* Check if NFS3 TCP detection is enabled. If it does not exist in
+     * the configuration file then it will be enabled by default. */
+    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
+
+        rs_nfs3_init(&sfc);
+
+        SCLogDebug("NFS3 TCP protocol detection enabled.");
+
+        AppLayerProtoDetectRegisterProtocol(ALPROTO_NFS3, proto_name);
+
+        if (RunmodeIsUnittests()) {
+
+            SCLogDebug("Unittest mode, registering default configuration.");
+            AppLayerProtoDetectPPRegister(IPPROTO_TCP, NFS3_DEFAULT_PORT,
+                ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN, STREAM_TOSERVER,
+                NFS3ProbingParser, NULL);
+
+        }
+        else {
+
+            if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
+                    proto_name, ALPROTO_NFS3, 0, NFS3_MIN_FRAME_LEN,
+                    NFS3ProbingParser, NULL)) {
+                SCLogDebug("No NFS3 app-layer configuration, enabling NFS3"
+                    " detection TCP detection on port %s.",
+                    NFS3_DEFAULT_PORT);
+                AppLayerProtoDetectPPRegister(IPPROTO_TCP,
+                    NFS3_DEFAULT_PORT, ALPROTO_NFS3, 0,
+                    NFS3_MIN_FRAME_LEN, STREAM_TOSERVER,
+                    NFS3ProbingParser, NULL);
+            }
+
+        }
+
+    }
+
+    else {
+        SCLogDebug("Protocol detecter and parser disabled for NFS3.");
+        return;
+    }
+
+    if (AppLayerParserConfParserEnabled("tcp", proto_name))
+    {
+        SCLogDebug("Registering NFS3 protocol parser.");
+
+        /* Register functions for state allocation and freeing. A
+         * state is allocated for every new NFS3 flow. */
+        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_NFS3,
+            NFS3StateAlloc, NFS3StateFree);
+
+        /* Register request parser for parsing frame from server to client. */
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_NFS3,
+            STREAM_TOSERVER, NFS3ParseRequest);
+
+        /* Register response parser for parsing frames from server to client. */
+        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_NFS3,
+            STREAM_TOCLIENT, NFS3ParseResponse);
+
+        /* Register a function to be called by the application layer
+         * when a transaction is to be freed. */
+        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_NFS3,
+            NFS3StateTxFree);
+
+        AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_NFS3,
+            NFS3GetTxLogged, NFS3SetTxLogged);
+
+        /* Register a function to return the current transaction count. */
+        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_NFS3,
+            NFS3GetTxCnt);
+
+        /* Transaction handling. */
+        AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_NFS3,
+            NFS3GetAlstateProgressCompletionStatus);
+        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP,
+            ALPROTO_NFS3, NFS3GetStateProgress);
+        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_NFS3,
+            NFS3GetTx);
+
+        AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_NFS3, NFS3GetFiles);
+
+        /* Application layer event handling. */
+//        AppLayerParserRegisterHasEventsFunc(IPPROTO_TCP, ALPROTO_NFS3,
+//            NFS3HasEvents);
+
+        /* What is this being registered for? */
+        AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_NFS3,
+            NULL, NFS3GetTxDetectState, NFS3SetTxDetectState);
+
+//        AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_NFS3,
+//            NFS3StateGetEventInfo);
+//        AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_NFS3,
+//            NFS3GetEvents);
+    }
+    else {
+        SCLogDebug("NFS3 protocol parsing disabled.");
+    }
+
+#ifdef UNITTESTS
+    AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_NFS3,
+        NFS3ParserRegisterTests);
+#endif
+}
+
+#ifdef UNITTESTS
+#endif
+
+void NFS3ParserRegisterTests(void)
+{
+#ifdef UNITTESTS
+#endif
+}
+
+#endif /* HAVE_RUST */
diff --git a/src/app-layer-nfs3.h b/src/app-layer-nfs3.h
new file mode 100644 (file)
index 0000000..9386c47
--- /dev/null
@@ -0,0 +1,34 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __APP_LAYER_NFS3_H__
+#define __APP_LAYER_NFS3_H__
+
+void RegisterNFS3Parsers(void);
+void NFS3ParserRegisterTests(void);
+
+/** Opaque Rust types. */
+typedef struct NFS3tate_ NFS3State;
+typedef struct NFS3Transaction_ NFS3Transaction;
+
+#endif /* __APP_LAYER_NFS3_H__ */
index a7840b5a3fcfddd129c04e6ed1b65fc5ce351c84..54cb5edde4a3d86ba8970a094cf7923e3705054f 100644 (file)
@@ -60,6 +60,7 @@
 #include "app-layer-modbus.h"
 #include "app-layer-enip.h"
 #include "app-layer-dnp3.h"
+#include "app-layer-nfs3.h"
 #include "app-layer-template.h"
 
 #include "conf.h"
@@ -1282,6 +1283,7 @@ void AppLayerParserRegisterProtocolParsers(void)
     RegisterENIPUDPParsers();
     RegisterENIPTCPParsers();
     RegisterDNP3Parsers();
+    RegisterNFS3Parsers();
     RegisterTemplateParsers();
 
     /** IMAP */
index 9830af5b2cbdc852906a9c4c986e7c15b0f19dc5..fb500d8f7a7bbd9bf13a3c55c41f02d6a21ef516 100644 (file)
@@ -81,6 +81,9 @@ const char *AppProtoToString(AppProto alproto)
         case ALPROTO_DNP3:
             proto_name = "dnp3";
             break;
+        case ALPROTO_NFS3:
+            proto_name = "nfs3";
+            break;
         case ALPROTO_TEMPLATE:
             proto_name = "template";
             break;
index bcecc79577e47cfe9ffabf77cbed0526923844e8..45a4cf4aa1b6aebe146ce1e9a63f134b72390119 100644 (file)
@@ -44,6 +44,7 @@ enum AppProtoEnum {
     ALPROTO_MODBUS,
     ALPROTO_ENIP,
     ALPROTO_DNP3,
+    ALPROTO_NFS3,
     ALPROTO_TEMPLATE,
 
     /* used by the probing parser when alproto detection fails
index 273328dec717d7fbf11b6b8052008d24440ccea7..180f2f3169ad2e0859c21f86e7110b0bcaed1e7f 100644 (file)
@@ -84,6 +84,13 @@ void DetectFilenameRegister(void)
             ALPROTO_SMTP, SIG_FLAG_TOSERVER, 0,
             DetectFileInspectGeneric);
 
+    DetectAppLayerInspectEngineRegister("files",
+            ALPROTO_NFS3, SIG_FLAG_TOSERVER, 0,
+            DetectFileInspectGeneric);
+    DetectAppLayerInspectEngineRegister("files",
+            ALPROTO_NFS3, SIG_FLAG_TOCLIENT, 0,
+            DetectFileInspectGeneric);
+
     g_file_match_list_id = DetectBufferTypeGetByName("files");
 
        SCLogDebug("registering filename rule option");
diff --git a/src/detect-nfs3-procedure.c b/src/detect-nfs3-procedure.c
new file mode 100644 (file)
index 0000000..1812c16
--- /dev/null
@@ -0,0 +1,641 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#include "suricata-common.h"
+#include "threads.h"
+#include "debug.h"
+#include "decode.h"
+#include "detect.h"
+
+#include "detect-parse.h"
+#include "detect-engine.h"
+#include "detect-engine-mpm.h"
+#include "detect-content.h"
+#include "detect-pcre.h"
+#include "detect-nfs3-procedure.h"
+
+#include "flow.h"
+#include "flow-util.h"
+#include "flow-var.h"
+
+#include "util-unittest.h"
+#include "util-unittest-helper.h"
+
+#ifndef HAVE_RUST
+void DetectNfs3ProcedureRegister(void)
+{
+}
+
+#else
+
+#include "app-layer-nfs3.h"
+#include "rust.h"
+#include "rust-nfs-nfs3-gen.h"
+
+/**
+ *   [nfs3_procedure]:[<|>]<proc>[<><proc>];
+ */
+#define PARSE_REGEX "^\\s*(<=|>=|<|>)?\\s*([0-9]+)\\s*(?:(<>)\\s*([0-9]+))?\\s*$"
+static pcre *parse_regex;
+static pcre_extra *parse_regex_study;
+
+enum DetectNfs3ProcedureMode {
+    PROCEDURE_EQ = 1, /* equal */
+    PROCEDURE_LT, /* less than */
+    PROCEDURE_LE, /* less than */
+    PROCEDURE_GT, /* greater than */
+    PROCEDURE_GE, /* greater than */
+    PROCEDURE_RA, /* range */
+};
+
+typedef struct DetectNfs3ProcedureData_ {
+    uint32_t lo;
+    uint32_t hi;
+    enum DetectNfs3ProcedureMode mode;
+} DetectNfs3ProcedureData;
+
+static DetectNfs3ProcedureData *DetectNfs3ProcedureParse (const char *);
+static int DetectNfs3ProcedureSetup (DetectEngineCtx *, Signature *s, const char *str);
+static void DetectNfs3ProcedureFree(void *);
+static void DetectNfs3ProcedureRegisterTests(void);
+static int g_nfs3_request_buffer_id = 0;
+
+static int DetectEngineInspectNfs3RequestGeneric(ThreadVars *tv,
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const Signature *s, const SigMatchData *smd,
+        Flow *f, uint8_t flags, void *alstate,
+        void *txv, uint64_t tx_id);
+
+static int DetectNfs3ProcedureMatch (ThreadVars *, DetectEngineThreadCtx *, Flow *,
+                                   uint8_t, void *, void *, const Signature *,
+                                   const SigMatchCtx *);
+
+/**
+ * \brief Registration function for nfs3_procedure keyword.
+ */
+void DetectNfs3ProcedureRegister (void)
+{
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].name = "nfs3_procedure";
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].desc = "match NFSv3 procedure";
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].url = DOC_URL DOC_VERSION "/rules/nfs3-keywords.html#procedure";
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].Match = NULL;
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].AppLayerTxMatch = DetectNfs3ProcedureMatch;
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].Setup = DetectNfs3ProcedureSetup;
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].Free = DetectNfs3ProcedureFree;
+    sigmatch_table[DETECT_AL_NFS3_PROCEDURE].RegisterTests = DetectNfs3ProcedureRegisterTests;
+
+
+    DetectSetupParseRegexes(PARSE_REGEX, &parse_regex, &parse_regex_study);
+
+    DetectAppLayerInspectEngineRegister("nfs3_request",
+            ALPROTO_NFS3, SIG_FLAG_TOSERVER, 0,
+            DetectEngineInspectNfs3RequestGeneric);
+
+    g_nfs3_request_buffer_id = DetectBufferTypeGetByName("nfs3_request");
+
+    SCLogDebug("g_nfs3_request_buffer_id %d", g_nfs3_request_buffer_id);
+}
+
+static int DetectEngineInspectNfs3RequestGeneric(ThreadVars *tv,
+        DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const Signature *s, const SigMatchData *smd,
+        Flow *f, uint8_t flags, void *alstate,
+        void *txv, uint64_t tx_id)
+{
+    return DetectEngineInspectGenericList(tv, de_ctx, det_ctx, s, smd,
+                                          f, flags, alstate, txv, tx_id);
+}
+
+static inline int
+ProcedureMatch(const uint32_t procedure,
+        enum DetectNfs3ProcedureMode mode, uint32_t lo, uint32_t hi)
+{
+    switch (mode) {
+        case PROCEDURE_EQ:
+            if (procedure == lo)
+                SCReturnInt(1);
+            break;
+        case PROCEDURE_LT:
+            if (procedure < lo)
+                SCReturnInt(1);
+            break;
+        case PROCEDURE_LE:
+            if (procedure <= lo)
+                SCReturnInt(1);
+            break;
+        case PROCEDURE_GT:
+            if (procedure > lo)
+                SCReturnInt(1);
+            break;
+        case PROCEDURE_GE:
+            if (procedure >= lo)
+                SCReturnInt(1);
+            break;
+        case PROCEDURE_RA:
+            if (procedure >= lo && procedure <= hi)
+                SCReturnInt(1);
+            break;
+    }
+    SCReturnInt(0);
+}
+
+/**
+ * \internal
+ * \brief Function to match procedure of a TX
+ *
+ * For 'file txs'
+ *
+ * \param t       Pointer to thread vars.
+ * \param det_ctx Pointer to the pattern matcher thread.
+ * \param f       Pointer to the current flow.
+ * \param flags   Flags.
+ * \param state   App layer state.
+ * \param s       Pointer to the Signature.
+ * \param m       Pointer to the sigmatch that we will cast into
+ *                DetectNfs3ProcedureData.
+ *
+ * \retval 0 no match.
+ * \retval 1 match.
+ */
+static int DetectNfs3ProcedureMatch (ThreadVars *t, DetectEngineThreadCtx *det_ctx,
+                                   Flow *f, uint8_t flags, void *state,
+                                   void *txv, const Signature *s,
+                                   const SigMatchCtx *ctx)
+{
+    SCEnter();
+
+    const DetectNfs3ProcedureData *dd = (const DetectNfs3ProcedureData *)ctx;
+    uint16_t i;
+    for (i = 0; i < 256; i++) {
+        uint32_t procedure;
+        if (rs_nfs3_tx_get_procedures(txv, i, &procedure) == 1) {
+            SCLogDebug("proc %u mode %u lo %u hi %u",
+                    procedure, dd->mode, dd->lo, dd->hi);
+            if (ProcedureMatch(procedure, dd->mode, dd->lo, dd->hi))
+                SCReturnInt(1);
+            continue;
+        }
+        break;
+    }
+    SCReturnInt(0);
+}
+
+/**
+ * \internal
+ * \brief Function to parse options passed via tls validity keywords.
+ *
+ * \param rawstr Pointer to the user provided options.
+ *
+ * \retval dd pointer to DetectNfs3ProcedureData on success.
+ * \retval NULL on failure.
+ */
+static DetectNfs3ProcedureData *DetectNfs3ProcedureParse (const char *rawstr)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+#define MAX_SUBSTRINGS 30
+    int ret = 0, res = 0;
+    int ov[MAX_SUBSTRINGS];
+    char mode[2] = "";
+    char value1[20] = "";
+    char value2[20] = "";
+    char range[3] = "";
+
+    ret = pcre_exec(parse_regex, parse_regex_study, rawstr, strlen(rawstr), 0,
+                    0, ov, MAX_SUBSTRINGS);
+    if (ret < 3 || ret > 5) {
+        SCLogError(SC_ERR_PCRE_MATCH, "Parse error %s", rawstr);
+        goto error;
+    }
+
+    res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 1, mode,
+                              sizeof(mode));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed");
+        goto error;
+    }
+    SCLogDebug("mode \"%s\"", mode);
+
+    res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 2, value1,
+                              sizeof(value1));
+    if (res < 0) {
+        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed");
+        goto error;
+    }
+    SCLogDebug("value1 \"%s\"", value1);
+
+    if (ret > 3) {
+        res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 3,
+                                  range, sizeof(range));
+        if (res < 0) {
+            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_copy_substring failed");
+            goto error;
+        }
+        SCLogDebug("range \"%s\"", range);
+
+        if (ret > 4) {
+            res = pcre_copy_substring((char *)rawstr, ov, MAX_SUBSTRINGS, 4,
+                                      value2, sizeof(value2));
+            if (res < 0) {
+                SCLogError(SC_ERR_PCRE_GET_SUBSTRING,
+                           "pcre_copy_substring failed");
+                goto error;
+            }
+            SCLogDebug("value2 \"%s\"", value2);
+        }
+    }
+
+    dd = SCCalloc(1, sizeof(DetectNfs3ProcedureData));
+    if (unlikely(dd == NULL))
+        goto error;
+
+    if (strlen(mode) == 1) {
+        if (mode[0] == '<')
+            dd->mode = PROCEDURE_LT;
+        else if (mode[0] == '>')
+            dd->mode = PROCEDURE_GT;
+    } else if (strlen(mode) == 2) {
+        if (strcmp(mode, "<=") == 0)
+            dd->mode = PROCEDURE_LE;
+        if (strcmp(mode, ">=") == 0)
+            dd->mode = PROCEDURE_GE;
+    }
+
+    if (strlen(range) > 0) {
+        if (strcmp("<>", range) == 0)
+            dd->mode = PROCEDURE_RA;
+    }
+
+    if (strlen(range) != 0 && strlen(mode) != 0) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT,
+                   "Range specified but mode also set");
+        goto error;
+    }
+
+    if (dd->mode == 0) {
+        dd->mode = PROCEDURE_EQ;
+    }
+
+    /* set the first value */
+    dd->lo = atoi(value1); //TODO
+
+    /* set the second value if specified */
+    if (strlen(value2) > 0) {
+        if (!(dd->mode == PROCEDURE_RA)) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT,
+                "Multiple tls validity values specified but mode is not range");
+            goto error;
+        }
+
+        //
+        dd->hi = atoi(value2); // TODO
+
+        if (dd->hi <= dd->lo) {
+            SCLogError(SC_ERR_INVALID_ARGUMENT,
+                "Second value in range must not be smaller than the first");
+            goto error;
+        }
+    }
+    return dd;
+
+error:
+    if (dd)
+        SCFree(dd);
+    return NULL;
+}
+
+
+
+/**
+ * \brief Function to add the parsed tls validity field into the current signature.
+ *
+ * \param de_ctx Pointer to the Detection Engine Context.
+ * \param s      Pointer to the Current Signature.
+ * \param rawstr Pointer to the user provided flags options.
+ * \param type   Defines if this is notBefore or notAfter.
+ *
+ * \retval 0 on Success.
+ * \retval -1 on Failure.
+ */
+static int DetectNfs3ProcedureSetup (DetectEngineCtx *de_ctx, Signature *s,
+                                   const char *rawstr)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    SigMatch *sm = NULL;
+
+    SCLogDebug("\'%s\'", rawstr);
+
+    if (DetectSignatureSetAppProto(s, ALPROTO_NFS3) != 0)
+        return -1;
+
+    dd = DetectNfs3ProcedureParse(rawstr);
+    if (dd == NULL) {
+        SCLogError(SC_ERR_INVALID_ARGUMENT,"Parsing \'%s\' failed", rawstr);
+        goto error;
+    }
+
+    /* okay so far so good, lets get this into a SigMatch
+     * and put it in the Signature. */
+    sm = SigMatchAlloc();
+    if (sm == NULL)
+        goto error;
+
+    sm->type = DETECT_AL_NFS3_PROCEDURE;
+    sm->ctx = (void *)dd;
+
+    s->flags |= SIG_FLAG_STATE_MATCH;
+    SCLogDebug("low %u hi %u", dd->lo, dd->hi);
+    SigMatchAppendSMToList(s, sm, g_nfs3_request_buffer_id);
+    return 0;
+
+error:
+    DetectNfs3ProcedureFree(dd);
+    return -1;
+}
+
+/**
+ * \internal
+ * \brief Function to free memory associated with DetectNfs3ProcedureData.
+ *
+ * \param de_ptr Pointer to DetectNfs3ProcedureData.
+ */
+void DetectNfs3ProcedureFree(void *ptr)
+{
+    SCFree(ptr);
+}
+
+#ifdef UNITTESTS
+
+/**
+ * \test This is a test for a valid value 1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse01 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("1430000000");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->mode == PROCEDURE_EQ);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value >1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse02 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse(">1430000000");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->mode == PROCEDURE_GT);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value <1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse03 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("<1430000000");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->mode == PROCEDURE_LT);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value 1430000000<>1470000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse04 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("1430000000<>1470000000");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->hi == 1470000000 &&
+                dd->mode == PROCEDURE_RA);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value A.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse05 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("A");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value >1430000000<>1470000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse06 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse(">1430000000<>1470000000");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value 1430000000<>.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse07 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("1430000000<>");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value <>1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse08 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("<>1430000000");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value "".
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse09 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value " ".
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse10 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse(" ");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a invalid value 1490000000<>1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse11 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("1490000000<>1430000000");
+    FAIL_IF_NOT_NULL(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value 1430000000 <> 1490000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse12 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("1430000000 <> 1490000000");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->hi == 1490000000 &&
+                dd->mode == PROCEDURE_RA);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value > 1430000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse13 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("> 1430000000 ");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1430000000 && dd->mode == PROCEDURE_GT);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value <   1490000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse14 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("<   1490000000 ");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1490000000 && dd->mode == PROCEDURE_LT);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+/**
+ * \test This is a test for a valid value    1490000000.
+ *
+ * \retval 1 on success.
+ * \retval 0 on failure.
+ */
+static int ValidityTestParse15 (void)
+{
+    DetectNfs3ProcedureData *dd = NULL;
+    dd = DetectNfs3ProcedureParse("   1490000000 ");
+    FAIL_IF_NULL(dd);
+    FAIL_IF_NOT(dd->lo == 1490000000 && dd->mode == PROCEDURE_EQ);
+    DetectNfs3ProcedureFree(dd);
+    PASS;
+}
+
+#endif /* UNITTESTS */
+
+/**
+ * \brief Register unit tests for nfs3_procedure.
+ */
+void DetectNfs3ProcedureRegisterTests(void)
+{
+#ifdef UNITTESTS /* UNITTESTS */
+    UtRegisterTest("ValidityTestParse01", ValidityTestParse01);
+    UtRegisterTest("ValidityTestParse02", ValidityTestParse02);
+    UtRegisterTest("ValidityTestParse03", ValidityTestParse03);
+    UtRegisterTest("ValidityTestParse04", ValidityTestParse04);
+    UtRegisterTest("ValidityTestParse05", ValidityTestParse05);
+    UtRegisterTest("ValidityTestParse06", ValidityTestParse06);
+    UtRegisterTest("ValidityTestParse07", ValidityTestParse07);
+    UtRegisterTest("ValidityTestParse08", ValidityTestParse08);
+    UtRegisterTest("ValidityTestParse09", ValidityTestParse09);
+    UtRegisterTest("ValidityTestParse10", ValidityTestParse10);
+    UtRegisterTest("ValidityTestParse11", ValidityTestParse11);
+    UtRegisterTest("ValidityTestParse12", ValidityTestParse12);
+    UtRegisterTest("ValidityTestParse13", ValidityTestParse13);
+    UtRegisterTest("ValidityTestParse14", ValidityTestParse14);
+    UtRegisterTest("ValidityTestParse15", ValidityTestParse15);
+#endif /* UNITTESTS */
+}
+#endif /* HAVE_RUST */
diff --git a/src/detect-nfs3-procedure.h b/src/detect-nfs3-procedure.h
new file mode 100644 (file)
index 0000000..61889d9
--- /dev/null
@@ -0,0 +1,30 @@
+/* Copyright (C) 2017 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author Victor Julien <victor@inliniac.net>
+ */
+
+#ifndef __DETECT_NFS3_PROCEDURE_H__
+#define __DETECT_NFS3_PROCEDURE_H__
+
+/* prototypes */
+void DetectNfs3ProcedureRegister (void);
+
+#endif /* __DETECT_NFS3_PROCEDURE_H__ */
index be63e61b364e448fb541918d2265c6a92509c2e6..de83999ce1916ef57cd2b85ea3158d30defd8ad3 100644 (file)
@@ -63,6 +63,8 @@
 #include "detect-http-hh.h"
 #include "detect-http-hrh.h"
 
+#include "detect-nfs3-procedure.h"
+
 #include "detect-engine-event.h"
 #include "decode.h"
 
@@ -3909,6 +3911,7 @@ void SigTableSetup(void)
     DetectTlsRegister();
     DetectTlsValidityRegister();
     DetectTlsVersionRegister();
+    DetectNfs3ProcedureRegister();
     DetectUrilenRegister();
     DetectDetectionFilterRegister();
     DetectAsn1Register();
index 28d703a03b3db0c5d6d8c2d82ca0b5cc183f472d..ccb99a017ec579de5ee2a188944a4f59e67d7a47 100644 (file)
@@ -1289,6 +1289,7 @@ enum {
     DETECT_AL_HTTP_RAW_HOST,
     DETECT_AL_HTTP_REQUEST_LINE,
     DETECT_AL_HTTP_RESPONSE_LINE,
+    DETECT_AL_NFS3_PROCEDURE,
     DETECT_AL_SSH_PROTOCOL,
     DETECT_AL_SSH_PROTOVERSION,
     DETECT_AL_SSH_SOFTWARE,
diff --git a/src/output-json-nfs3.c b/src/output-json-nfs3.c
new file mode 100644 (file)
index 0000000..20f1d4d
--- /dev/null
@@ -0,0 +1,202 @@
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/*
+ * TODO: Update \author in this file and in output-json-nfs3.h.
+ * TODO: Remove SCLogNotice statements, or convert to debug.
+ * TODO: Implement your app-layers logging.
+ */
+
+/**
+ * \file
+ *
+ * \author FirstName LastName <yourname@domain>
+ *
+ * Implement JSON/eve logging app-layer NFS3.
+ */
+
+#include "suricata-common.h"
+#include "debug.h"
+#include "detect.h"
+#include "pkt-var.h"
+#include "conf.h"
+
+#include "threads.h"
+#include "threadvars.h"
+#include "tm-threads.h"
+
+#include "util-unittest.h"
+#include "util-buffer.h"
+#include "util-debug.h"
+#include "util-byte.h"
+
+#include "output.h"
+#include "output-json.h"
+
+#include "app-layer.h"
+#include "app-layer-parser.h"
+
+#include "app-layer-nfs3.h"
+#include "output-json-nfs3.h"
+
+#ifdef HAVE_RUST
+#ifdef HAVE_LIBJANSSON
+#include "rust-nfs-log-gen.h"
+
+typedef struct LogNFS3FileCtx_ {
+    LogFileCtx *file_ctx;
+    uint32_t    flags;
+} LogNFS3FileCtx;
+
+typedef struct LogNFS3LogThread_ {
+    LogNFS3FileCtx *nfs3log_ctx;
+    uint32_t            count;
+    MemBuffer          *buffer;
+} LogNFS3LogThread;
+
+static int JsonNFS3Logger(ThreadVars *tv, void *thread_data,
+    const Packet *p, Flow *f, void *state, void *tx, uint64_t tx_id)
+{
+    NFS3Transaction *nfs3tx = tx;
+    LogNFS3LogThread *thread = thread_data;
+    json_t *js, *nfs3js;
+
+    if (rs_nfs3_tx_logging_is_filtered(nfs3tx))
+        return TM_ECODE_OK;
+
+    js = CreateJSONHeader((Packet *)p, 0, "nfs3");
+    if (unlikely(js == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    nfs3js = rs_nfs3_log_json_response(tx);
+    if (unlikely(nfs3js == NULL)) {
+        goto error;
+    }
+
+    json_object_set_new(js, "nfs3", nfs3js);
+
+    MemBufferReset(thread->buffer);
+    OutputJSONBuffer(js, thread->nfs3log_ctx->file_ctx, &thread->buffer);
+
+    json_decref(js);
+    return TM_ECODE_OK;
+
+error:
+    json_decref(js);
+    return TM_ECODE_FAILED;
+}
+
+static void OutputNFS3LogDeInitCtxSub(OutputCtx *output_ctx)
+{
+    LogNFS3FileCtx *nfs3log_ctx = (LogNFS3FileCtx *)output_ctx->data;
+    SCFree(nfs3log_ctx);
+    SCFree(output_ctx);
+}
+
+static OutputCtx *OutputNFS3LogInitSub(ConfNode *conf,
+    OutputCtx *parent_ctx)
+{
+    AlertJsonThread *ajt = parent_ctx->data;
+
+    LogNFS3FileCtx *nfs3log_ctx = SCCalloc(1, sizeof(*nfs3log_ctx));
+    if (unlikely(nfs3log_ctx == NULL)) {
+        return NULL;
+    }
+    nfs3log_ctx->file_ctx = ajt->file_ctx;
+
+    OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx));
+    if (unlikely(output_ctx == NULL)) {
+        SCFree(nfs3log_ctx);
+        return NULL;
+    }
+    output_ctx->data = nfs3log_ctx;
+    output_ctx->DeInit = OutputNFS3LogDeInitCtxSub;
+
+    SCLogDebug("NFS3 log sub-module initialized.");
+
+    AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_NFS3);
+
+    return output_ctx;
+}
+
+#define OUTPUT_BUFFER_SIZE 65535
+
+static TmEcode JsonNFS3LogThreadInit(ThreadVars *t, const void *initdata, void **data)
+{
+    LogNFS3LogThread *thread = SCCalloc(1, sizeof(*thread));
+    if (unlikely(thread == NULL)) {
+        return TM_ECODE_FAILED;
+    }
+
+    if (initdata == NULL) {
+        SCLogDebug("Error getting context for EveLogNFS3.  \"initdata\" is NULL.");
+        SCFree(thread);
+        return TM_ECODE_FAILED;
+    }
+
+    thread->buffer = MemBufferCreateNew(OUTPUT_BUFFER_SIZE);
+    if (unlikely(thread->buffer == NULL)) {
+        SCFree(thread);
+        return TM_ECODE_FAILED;
+    }
+
+    thread->nfs3log_ctx = ((OutputCtx *)initdata)->data;
+    *data = (void *)thread;
+
+    return TM_ECODE_OK;
+}
+
+static TmEcode JsonNFS3LogThreadDeinit(ThreadVars *t, void *data)
+{
+    LogNFS3LogThread *thread = (LogNFS3LogThread *)data;
+    if (thread == NULL) {
+        return TM_ECODE_OK;
+    }
+    if (thread->buffer != NULL) {
+        MemBufferFree(thread->buffer);
+    }
+    SCFree(thread);
+    return TM_ECODE_OK;
+}
+
+void JsonNFS3LogRegister(void)
+{
+    /* Register as an eve sub-module. */
+    OutputRegisterTxSubModule(LOGGER_JSON_NFS3, "eve-log", "JsonNFS3Log",
+        "eve-log.nfs3", OutputNFS3LogInitSub, ALPROTO_NFS3,
+        JsonNFS3Logger, JsonNFS3LogThreadInit,
+        JsonNFS3LogThreadDeinit, NULL);
+
+    SCLogDebug("NFS3 JSON logger registered.");
+}
+
+#else /* No JSON support. */
+
+void JsonNFS3LogRegister(void)
+{
+}
+
+#endif /* HAVE_LIBJANSSON */
+
+#else /* no rust */
+
+void JsonNFS3LogRegister(void)
+{
+}
+
+#endif /* HAVE_RUST */
diff --git a/src/output-json-nfs3.h b/src/output-json-nfs3.h
new file mode 100644 (file)
index 0000000..c2301b1
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2015 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+/**
+ * \file
+ *
+ * \author FirstName LastName <name@domain>
+ */
+
+#ifndef __OUTPUT_JSON_NFS3_H__
+#define __OUTPUT_JSON_NFS3_H__
+
+void JsonNFS3LogRegister(void);
+
+#endif /* __OUTPUT_JSON_NFS3_H__ */
index 8a22b0ed0f25ccee688b034ea8e0bd7296760b11..c8f91c4b1ecea719ddb8c738d95478240ab05c73 100644 (file)
@@ -68,6 +68,7 @@
 #include "log-tcp-data.h"
 #include "log-stats.h"
 #include "output-json.h"
+#include "output-json-nfs3.h"
 #include "output-json-template.h"
 #include "output-lua.h"
 #include "output-json-dnp3.h"
@@ -1081,6 +1082,8 @@ void OutputRegisterLoggers(void)
     JsonDNP3LogRegister();
     JsonVarsLogRegister();
 
+    /* NFS3 JSON logger. */
+    JsonNFS3LogRegister();
     /* Template JSON logger. */
     JsonTemplateLogRegister();
 }
index f69986008db9fe5f61f5d4ba609c23dde56decc8..aaf47dcef76b1bf0586002314693ddc44b9781b5 100644 (file)
@@ -39,4 +39,13 @@ typedef struct SuricataContext_ {
 
 } SuricataContext;
 
+typedef struct SuricataFileContext_ {
+
+    const StreamingBufferConfig *sbcfg;
+
+} SuricataFileContext;
+
+struct _Store;
+typedef struct _Store Store;
+
 #endif /* !__RUST_H__ */
index 0af2078d64bd7c1caa0f898af5a6447edff8d6f9..181279c357f36c923cf7b4729c2fe92b996d4604 100644 (file)
@@ -396,6 +396,7 @@ typedef enum {
     LOGGER_JSON_HTTP,
     LOGGER_JSON_SMTP,
     LOGGER_JSON_TLS,
+    LOGGER_JSON_NFS3,
     LOGGER_JSON_TEMPLATE,
     LOGGER_TLS_STORE,
     LOGGER_TLS,
index e5932030ef314822fba1eff6a670db207c07420c..4e873844084a9eb722f3f6247145411444b31396 100644 (file)
@@ -235,6 +235,8 @@ outputs:
             # to yes
             #md5: [body, subject]
 
+        #- dnp3
+        #- nfs3
         - ssh
         - stats:
             totals: yes       # stats for all threads merged together
@@ -244,7 +246,6 @@ outputs:
         - flow
         # uni-directional flows
         #- netflow
-        #- dnp3
         # Vars log flowbits and other packet and flow vars
         #- vars
 
@@ -675,6 +676,8 @@ pcap-file:
 # "detection-only" enables protocol detection only (parser disabled).
 app-layer:
   protocols:
+    nfs3:
+      enabled: yes
     tls:
       enabled: yes
       detection-ports: