pub mod lua;
pub mod dns;
+pub mod nfs;
--- /dev/null
+/* 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();
+}
--- /dev/null
+/* 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;
--- /dev/null
+/* 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)
+}
+
--- /dev/null
+/* 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,
+ }
+ ))
+);
--- /dev/null
+/* 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()
+}
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 \
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 \
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 \
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)
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)
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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__ */
#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"
RegisterENIPUDPParsers();
RegisterENIPTCPParsers();
RegisterDNP3Parsers();
+ RegisterNFS3Parsers();
RegisterTemplateParsers();
/** IMAP */
case ALPROTO_DNP3:
proto_name = "dnp3";
break;
+ case ALPROTO_NFS3:
+ proto_name = "nfs3";
+ break;
case ALPROTO_TEMPLATE:
proto_name = "template";
break;
ALPROTO_MODBUS,
ALPROTO_ENIP,
ALPROTO_DNP3,
+ ALPROTO_NFS3,
ALPROTO_TEMPLATE,
/* used by the probing parser when alproto detection fails
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");
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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__ */
#include "detect-http-hh.h"
#include "detect-http-hrh.h"
+#include "detect-nfs3-procedure.h"
+
#include "detect-engine-event.h"
#include "decode.h"
DetectTlsRegister();
DetectTlsValidityRegister();
DetectTlsVersionRegister();
+ DetectNfs3ProcedureRegister();
DetectUrilenRegister();
DetectDetectionFilterRegister();
DetectAsn1Register();
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,
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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__ */
#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"
JsonDNP3LogRegister();
JsonVarsLogRegister();
+ /* NFS3 JSON logger. */
+ JsonNFS3LogRegister();
/* Template JSON logger. */
JsonTemplateLogRegister();
}
} SuricataContext;
+typedef struct SuricataFileContext_ {
+
+ const StreamingBufferConfig *sbcfg;
+
+} SuricataFileContext;
+
+struct _Store;
+typedef struct _Store Store;
+
#endif /* !__RUST_H__ */
LOGGER_JSON_HTTP,
LOGGER_JSON_SMTP,
LOGGER_JSON_TLS,
+ LOGGER_JSON_NFS3,
LOGGER_JSON_TEMPLATE,
LOGGER_TLS_STORE,
LOGGER_TLS,
# to yes
#md5: [body, subject]
+ #- dnp3
+ #- nfs3
- ssh
- stats:
totals: yes # stats for all threads merged together
- flow
# uni-directional flows
#- netflow
- #- dnp3
# Vars log flowbits and other packet and flow vars
#- vars
# "detection-only" enables protocol detection only (parser disabled).
app-layer:
protocols:
+ nfs3:
+ enabled: yes
tls:
enabled: yes
detection-ports: