summaryrefslogtreecommitdiffstats
path: root/rust/src/nfs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:39:49 +0000
commita0aa2307322cd47bbf416810ac0292925e03be87 (patch)
tree37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/nfs
parentInitial commit. (diff)
downloadsuricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz
suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/nfs')
-rw-r--r--rust/src/nfs/log.rs180
-rw-r--r--rust/src/nfs/mod.rs33
-rw-r--r--rust/src/nfs/nfs.rs2121
-rw-r--r--rust/src/nfs/nfs2.rs122
-rw-r--r--rust/src/nfs/nfs2_records.rs235
-rw-r--r--rust/src/nfs/nfs3.rs274
-rw-r--r--rust/src/nfs/nfs3_records.rs1000
-rw-r--r--rust/src/nfs/nfs4.rs425
-rw-r--r--rust/src/nfs/nfs4_records.rs2074
-rw-r--r--rust/src/nfs/nfs_records.rs29
-rw-r--r--rust/src/nfs/rpc_records.rs465
-rw-r--r--rust/src/nfs/types.rs293
12 files changed, 7251 insertions, 0 deletions
diff --git a/rust/src/nfs/log.rs b/rust/src/nfs/log.rs
new file mode 100644
index 0000000..f6fdc8f
--- /dev/null
+++ b/rust/src/nfs/log.rs
@@ -0,0 +1,180 @@
+/* Copyright (C) 2017-2020 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.
+ */
+
+use std::string::String;
+use crate::jsonbuilder::{JsonBuilder, JsonError};
+use crate::nfs::types::*;
+use crate::nfs::nfs::*;
+use crc::crc32;
+
+#[no_mangle]
+pub extern "C" fn rs_nfs_tx_logging_is_filtered(state: &mut NFSState,
+ tx: &mut NFSTransaction)
+ -> u8
+{
+ // TODO probably best to make this configurable
+
+ if state.nfs_version <= 3 && tx.procedure == NFSPROC3_GETATTR {
+ return 1;
+ }
+
+ return 0;
+}
+
+fn nfs_rename_object(tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ let from_str = String::from_utf8_lossy(&tx.file_name);
+ js.set_string("from", &from_str)?;
+
+ let to_vec = match tx.type_data {
+ Some(NFSTransactionTypeData::RENAME(ref x)) => { x.to_vec() },
+ _ => { Vec::new() }
+ };
+
+ let to_str = String::from_utf8_lossy(&to_vec);
+ js.set_string("to", &to_str)?;
+ Ok(())
+}
+
+fn nfs_creds_object(tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ let mach_name = String::from_utf8_lossy(&tx.request_machine_name);
+ js.set_string("machine_name", &mach_name)?;
+ js.set_uint("uid", tx.request_uid as u64)?;
+ js.set_uint("gid", tx.request_gid as u64)?;
+ Ok(())
+}
+
+fn nfs_file_object(tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ js.set_bool("first", tx.is_first)?;
+ js.set_bool("last", tx.is_last)?;
+
+ if let Some(NFSTransactionTypeData::FILE(ref tdf)) = tx.type_data {
+ js.set_uint("last_xid", tdf.file_last_xid as u64)?;
+ js.set_uint("chunks", tdf.chunk_count as u64)?;
+ }
+ Ok(())
+}
+/*
+fn nfs_handle2hex(bytes: &Vec<u8>) -> String {
+ let strings: Vec<String> = bytes.iter()
+ .map(|b| format!("{:02x}", b))
+ .collect();
+ strings.join("")
+}
+*/
+fn nfs_handle2crc(bytes: &[u8]) -> u32 {
+ let c = crc32::checksum_ieee(bytes);
+ c
+}
+
+fn nfs_common_header(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ js.set_uint("version", state.nfs_version as u64)?;
+ let proc_string = if state.nfs_version < 4 {
+ nfs3_procedure_string(tx.procedure)
+ } else {
+ nfs4_procedure_string(tx.procedure)
+ };
+ js.set_string("procedure", &proc_string)?;
+ let file_name = String::from_utf8_lossy(&tx.file_name);
+ js.set_string("filename", &file_name)?;
+
+ if !tx.file_handle.is_empty() {
+ //js.set_string("handle", &nfs_handle2hex(&tx.file_handle));
+ let c = nfs_handle2crc(&tx.file_handle);
+ let s = format!("{:x}", c);
+ js.set_string("hhash", &s)?;
+ }
+ js.set_uint("id", tx.id)?;
+ js.set_bool("file_tx", tx.is_file_tx)?;
+ Ok(())
+}
+
+fn nfs_log_request(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ nfs_common_header(state, tx, js)?;
+ js.set_string("type", "request")?;
+ Ok(())
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs_log_json_request(state: &mut NFSState, tx: &mut NFSTransaction,
+ js: &mut JsonBuilder) -> bool
+{
+ nfs_log_request(state, tx, js).is_ok()
+}
+
+fn nfs_log_response(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ nfs_common_header(state, tx, js)?;
+ js.set_string("type", "response")?;
+
+ js.set_string("status", &nfs3_status_string(tx.nfs_response_status))?;
+
+ if state.nfs_version <= 3 {
+ if tx.procedure == NFSPROC3_READ {
+ js.open_object("read")?;
+ nfs_file_object(tx, js)?;
+ js.close()?;
+ } else if tx.procedure == NFSPROC3_WRITE {
+ js.open_object("write")?;
+ nfs_file_object(tx, js)?;
+ js.close()?;
+ } else if tx.procedure == NFSPROC3_RENAME {
+ js.open_object("rename")?;
+ nfs_rename_object(tx, js)?;
+ js.close()?;
+ }
+ }
+ Ok(())
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs_log_json_response(state: &mut NFSState, tx: &mut NFSTransaction,
+ js: &mut JsonBuilder) -> bool
+{
+ nfs_log_response(state, tx, js).is_ok()
+}
+
+fn rpc_log_response(tx: &NFSTransaction, js: &mut JsonBuilder)
+ -> Result<(), JsonError>
+{
+ js.set_uint("xid", tx.xid as u64)?;
+ js.set_string("status", &rpc_status_string(tx.rpc_response_status))?;
+ js.set_string("auth_type", &rpc_auth_type_string(tx.auth_type))?;
+ if tx.auth_type == RPCAUTH_UNIX {
+ js.open_object("creds")?;
+ nfs_creds_object(tx, js)?;
+ js.close()?;
+ }
+ Ok(())
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rpc_log_json_response(tx: &mut NFSTransaction,
+ js: &mut JsonBuilder) -> bool
+{
+ rpc_log_response(tx, js).is_ok()
+}
diff --git a/rust/src/nfs/mod.rs b/rust/src/nfs/mod.rs
new file mode 100644
index 0000000..2f6fe84
--- /dev/null
+++ b/rust/src/nfs/mod.rs
@@ -0,0 +1,33 @@
+/* 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.
+ */
+
+//! NFS application layer, parser, logger module.
+
+pub mod types;
+pub mod rpc_records;
+pub mod nfs_records;
+pub mod nfs2_records;
+pub mod nfs3_records;
+pub mod nfs4_records;
+pub mod nfs;
+pub mod nfs2;
+pub mod nfs3;
+pub mod nfs4;
+pub mod log;
+
+//#[cfg(feature = "lua")]
+//pub mod lua;
diff --git a/rust/src/nfs/nfs.rs b/rust/src/nfs/nfs.rs
new file mode 100644
index 0000000..dfb5e0e
--- /dev/null
+++ b/rust/src/nfs/nfs.rs
@@ -0,0 +1,2121 @@
+/* Copyright (C) 2017-2021 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
+
+use std;
+use std::cmp;
+use std::collections::HashMap;
+use std::ffi::CString;
+
+use nom7::{Err, Needed};
+
+use crate::applayer;
+use crate::applayer::*;
+use crate::frames::*;
+use crate::core::*;
+use crate::conf::*;
+use crate::filetracker::*;
+use crate::filecontainer::*;
+
+use crate::nfs::types::*;
+use crate::nfs::rpc_records::*;
+use crate::nfs::nfs_records::*;
+use crate::nfs::nfs2_records::*;
+use crate::nfs::nfs3_records::*;
+
+pub static mut SURICATA_NFS_FILE_CONFIG: Option<&'static SuricataFileContext> = None;
+
+pub const NFS_MIN_FRAME_LEN: u16 = 32;
+
+static mut NFS_MAX_TX: usize = 1024;
+
+pub const RPC_TCP_PRE_CREDS: usize = 28;
+pub const RPC_UDP_PRE_CREDS: usize = 24;
+
+static mut ALPROTO_NFS: AppProto = ALPROTO_UNKNOWN;
+/*
+ * 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 NFSState::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
+ * NFSTransaction where they can be looked up per handle as part of the
+ * Transaction lookup.
+ */
+
+#[derive(AppLayerFrameType)]
+pub enum NFSFrameType {
+ RPCPdu,
+ RPCHdr,
+ RPCData,
+ RPCCreds, // for rpc calls | rpc.creds [creds_flavor + creds_len + creds]
+
+ NFSPdu,
+ NFSStatus,
+
+ NFS4Pdu,
+ NFS4Hdr,
+ NFS4Ops,
+ NFS4Status,
+}
+
+#[derive(FromPrimitive, Debug, AppLayerEvent)]
+pub enum NFSEvent {
+ MalformedData = 0,
+ NonExistingVersion = 1,
+ UnsupportedVersion = 2,
+ TooManyTransactions = 3,
+}
+
+#[derive(Debug)]
+pub enum NFSTransactionTypeData {
+ RENAME(Vec<u8>),
+ FILE(NFSTransactionFile),
+}
+
+#[derive(Default, Debug)]
+pub struct NFSTransactionFile {
+ /// file transactions are unidirectional in the sense that they track
+ /// a single file on one direction
+ pub direction: Direction, // Direction::ToClient or Direction::ToServer
+
+ /// additional procedures part of a single file transfer. Currently
+ /// only COMMIT on WRITEs.
+ pub file_additional_procs: Vec<u32>,
+
+ pub chunk_count: u32,
+
+ /// last xid of this file transfer. Last READ or COMMIT normally.
+ pub file_last_xid: u32,
+
+ /// after a gap, this will be set to a time in the future. If the file
+ /// receives no updates before that, it will be considered complete.
+ pub post_gap_ts: u64,
+
+ /// 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: FileTransferTracker,
+}
+
+impl NFSTransactionFile {
+ pub fn new() -> Self {
+ return Self {
+ file_tracker: FileTransferTracker::new(),
+ ..Default::default()
+ }
+ }
+ pub fn update_file_flags(&mut self, flow_file_flags: u16) {
+ let dir_flag = if self.direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT };
+ self.file_tracker.file_flags = unsafe { FileFlowFlagsToFlags(flow_file_flags, dir_flag) };
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_gettxfiles(_state: *mut std::ffi::c_void, tx: *mut std::ffi::c_void, direction: u8) -> AppLayerGetFileState {
+ let tx = cast_pointer!(tx, NFSTransaction);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ let tx_dir : u8 = tdf.direction.into();
+ if direction & tx_dir != 0 {
+ if let Some(sfcm) = { SURICATA_NFS_FILE_CONFIG } {
+ return AppLayerGetFileState { fc: &mut tdf.file_tracker.file, cfg: sfcm.files_sbcfg }
+ }
+ }
+ }
+ AppLayerGetFileState::err()
+}
+
+#[derive(Debug)]
+pub struct NFSTransaction {
+ pub id: u64, /// internal id
+ pub xid: u32, /// nfs req/reply pair id
+ pub procedure: u32,
+ /// file name of the object we're dealing with. In case of RENAME
+ /// this is the 'from' or original name.
+ pub file_name: Vec<u8>,
+
+ pub auth_type: u32,
+ pub request_machine_name: Vec<u8>,
+ pub request_uid: u32,
+ pub request_gid: u32,
+
+ pub rpc_response_status: u32,
+ pub nfs_response_status: u32,
+
+ pub is_first: bool,
+ pub is_last: bool,
+
+ /// for state tracking. false means this side is in progress, true
+ /// that it's complete.
+ pub request_done: bool,
+ pub response_done: bool,
+
+ pub nfs_version: u16,
+
+ /// is a special file tx that we look up by file_handle instead of XID
+ pub is_file_tx: bool,
+ pub is_file_closed: bool,
+ pub file_handle: Vec<u8>,
+
+ /// Procedure type specific data
+ /// TODO see if this can be an `Option<Box<NFSTransactionTypeData>>`. Initial
+ /// attempt failed.
+ pub type_data: Option<NFSTransactionTypeData>,
+
+ pub tx_data: AppLayerTxData,
+}
+
+impl Default for NFSTransaction {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl NFSTransaction {
+ pub fn new() -> Self {
+ return Self {
+ id: 0,
+ xid: 0,
+ procedure: 0,
+ file_name:Vec::new(),
+ request_machine_name:Vec::new(),
+ request_uid:0,
+ request_gid:0,
+ rpc_response_status:0,
+ nfs_response_status:0,
+ auth_type: 0,
+ is_first: false,
+ is_last: false,
+ request_done: false,
+ response_done: false,
+ nfs_version:0,
+ is_file_tx: false,
+ is_file_closed: false,
+ file_handle:Vec::new(),
+ type_data: None,
+ tx_data: AppLayerTxData::new(),
+ }
+ }
+
+ pub fn free(&mut self) {
+ debug_validate_bug_on!(self.tx_data.files_opened > 1);
+ debug_validate_bug_on!(self.tx_data.files_logged > 1);
+ }
+}
+
+impl Transaction for NFSTransaction {
+ fn id(&self) -> u64 {
+ self.id
+ }
+}
+
+impl Drop for NFSTransaction {
+ fn drop(&mut self) {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = self.type_data {
+ if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } {
+ tdf.file_tracker.file.free(sfcm);
+ }
+ }
+ self.free();
+ }
+}
+
+#[derive(Debug)]
+pub struct NFSRequestXidMap {
+ pub progver: u32,
+ pub procedure: u32,
+ pub chunk_offset: u64,
+ pub file_name:Vec<u8>,
+
+ /// READ replies can use this to get to the handle the request used
+ pub file_handle:Vec<u8>,
+
+ pub gssapi_proc: u32,
+ pub gssapi_service: u32,
+}
+
+impl NFSRequestXidMap {
+ pub fn new(progver: u32, procedure: u32, chunk_offset: u64) -> NFSRequestXidMap {
+ NFSRequestXidMap {
+ progver,
+ procedure,
+ chunk_offset,
+ file_name:Vec::new(),
+ file_handle:Vec::new(),
+ gssapi_proc: 0,
+ gssapi_service: 0,
+ }
+ }
+}
+
+/// little wrapper around the FileTransferTracker::new_chunk method
+pub fn filetracker_newchunk(ft: &mut FileTransferTracker, name: &[u8], data: &[u8],
+ chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32)
+{
+ if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } {
+ ft.new_chunk(sfcm, name, data, chunk_offset,
+ chunk_size, fill_bytes, is_last, xid);
+ }
+}
+
+fn filetracker_trunc(ft: &mut FileTransferTracker)
+{
+ if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } {
+ ft.trunc(sfcm);
+ }
+}
+
+pub fn filetracker_close(ft: &mut FileTransferTracker)
+{
+ if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } {
+ ft.close(sfcm);
+ }
+}
+
+fn filetracker_update(ft: &mut FileTransferTracker, data: &[u8], gap_size: u32) -> u32
+{
+ if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } {
+ ft.update(sfcm, data, gap_size)
+ } else {
+ 0
+ }
+}
+
+
+#[derive(Debug)]
+pub struct NFSState {
+ state_data: AppLayerStateData,
+
+ /// map xid to procedure so replies can lookup the procedure
+ pub requestmap: HashMap<u32, NFSRequestXidMap>,
+
+ /// map file handle (1) to name (2)
+ pub namemap: HashMap<Vec<u8>, Vec<u8>>,
+
+ /// transactions list
+ pub transactions: Vec<NFSTransaction>,
+
+ /// partial record tracking
+ pub ts_chunk_xid: u32,
+ pub tc_chunk_xid: u32,
+ /// size of the current chunk that we still need to receive
+ pub ts_chunk_left: u32,
+ pub tc_chunk_left: u32,
+ /// file handle of in progress toserver WRITE file chunk
+ ts_chunk_fh: Vec<u8>,
+
+ ts_ssn_gap: bool,
+ tc_ssn_gap: bool,
+
+ ts_gap: bool, // last TS update was gap
+ tc_gap: bool, // last TC update was gap
+
+ is_udp: bool,
+
+ /// true as long as we have file txs that are in a post-gap
+ /// state. It means we'll do extra house keeping for those.
+ check_post_gap_file_txs: bool,
+ post_gap_files_checked: bool,
+
+ pub nfs_version: u16,
+
+ /// tx counter for assigning incrementing id's to tx's
+ tx_id: u64,
+
+ /// Timestamp in seconds of last update. This is packet time,
+ /// potentially coming from pcaps.
+ ts: u64,
+}
+
+impl Default for NFSState {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl State<NFSTransaction> for NFSState {
+ fn get_transaction_count(&self) -> usize {
+ self.transactions.len()
+ }
+
+ fn get_transaction_by_index(&self, index: usize) -> Option<&NFSTransaction> {
+ self.transactions.get(index)
+ }
+}
+
+impl NFSState {
+ /// Allocation function for a new TLS parser instance
+ pub fn new() -> NFSState {
+ NFSState {
+ state_data: AppLayerStateData::new(),
+ requestmap:HashMap::new(),
+ namemap:HashMap::new(),
+ transactions: Vec::new(),
+ ts_chunk_xid:0,
+ tc_chunk_xid:0,
+ ts_chunk_left:0,
+ tc_chunk_left:0,
+ ts_chunk_fh:Vec::new(),
+ ts_ssn_gap:false,
+ tc_ssn_gap:false,
+ ts_gap:false,
+ tc_gap:false,
+ is_udp:false,
+ check_post_gap_file_txs:false,
+ post_gap_files_checked:false,
+ nfs_version:0,
+ tx_id:0,
+ ts: 0,
+ }
+ }
+
+ fn update_ts(&mut self, ts: u64) {
+ if ts != self.ts {
+ self.ts = ts;
+ self.post_gap_files_checked = false;
+ }
+ }
+
+ pub fn new_tx(&mut self) -> NFSTransaction {
+ let mut tx = NFSTransaction::new();
+ self.tx_id += 1;
+ tx.id = self.tx_id;
+ if self.transactions.len() > unsafe { NFS_MAX_TX } {
+ // set at least one another transaction to the drop state
+ for tx_old in &mut self.transactions {
+ if !tx_old.request_done || !tx_old.response_done {
+ tx_old.request_done = true;
+ tx_old.response_done = true;
+ tx_old.is_file_closed = true;
+ tx_old.tx_data.set_event(NFSEvent::TooManyTransactions as u8);
+ break;
+ }
+ }
+ }
+ 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.transactions.remove(index);
+ }
+ }
+
+ pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NFSTransaction> {
+ SCLogDebug!("get_tx_by_id: tx_id={}", tx_id);
+ for tx in &mut self.transactions {
+ if tx.id == tx_id + 1 {
+ SCLogDebug!("Found NFS TX with ID {}", tx_id);
+ return Some(tx);
+ }
+ }
+ SCLogDebug!("Failed to find NFS TX with ID {}", tx_id);
+ return None;
+ }
+
+ pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFSTransaction> {
+ 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 NFS TX with ID {} XID {:04X}", tx.id, tx.xid);
+ return Some(tx);
+ }
+ }
+ SCLogDebug!("Failed to find NFS TX with XID {:04X}", tx_xid);
+ return None;
+ }
+
+ /// Set an event. The event is set on the most recent transaction.
+ pub fn set_event(&mut self, event: NFSEvent) {
+ let len = self.transactions.len();
+ if len == 0 {
+ return;
+ }
+
+ let tx = &mut self.transactions[len - 1];
+ tx.tx_data.set_event(event as u8);
+ }
+
+ // TODO maybe not enough users to justify a func
+ pub fn mark_response_tx_done(&mut self, xid: u32, rpc_status: u32, nfs_status: u32, resp_handle: &Vec<u8>)
+ {
+ if let Some(mytx) = self.get_tx_by_xid(xid) {
+ mytx.response_done = true;
+ mytx.rpc_response_status = rpc_status;
+ mytx.nfs_response_status = nfs_status;
+ if mytx.file_handle.is_empty() && !resp_handle.is_empty() {
+ mytx.file_handle = resp_handle.to_vec();
+ }
+
+ SCLogDebug!("process_reply_record: tx ID {} XID {:04X} REQUEST {} RESPONSE {}",
+ mytx.id, mytx.xid, mytx.request_done, mytx.response_done);
+ } else {
+ //SCLogNotice!("process_reply_record: not TX found for XID {}", r.hdr.xid);
+ }
+ }
+
+ fn add_rpc_udp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> {
+ let rpc_udp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8);
+ SCLogDebug!("rpc_udp_pdu ts frame {:?}", rpc_udp_ts_pdu);
+ rpc_udp_ts_pdu
+ }
+
+ fn add_rpc_udp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) {
+ let _rpc_udp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8);
+ SCLogDebug!("rpc_creds ts frame {:?}", _rpc_udp_ts_creds);
+ }
+
+ fn add_rpc_tcp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> {
+ let rpc_tcp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8);
+ SCLogDebug!("rpc_tcp_pdu ts frame {:?}", rpc_tcp_ts_pdu);
+ rpc_tcp_ts_pdu
+ }
+
+ fn add_rpc_tcp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) {
+ let _rpc_tcp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8);
+ SCLogDebug!("rpc_tcp_ts_creds {:?}", _rpc_tcp_ts_creds);
+ }
+
+ fn add_nfs_ts_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) {
+ let _nfs_req_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8);
+ SCLogDebug!("nfs_ts_pdu Frame {:?}", _nfs_req_pdu);
+ }
+
+ fn add_nfs4_ts_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) {
+ let _nfs4_ts_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8);
+ SCLogDebug!("nfs4_ts_pdu Frame: {:?}", _nfs4_ts_pdu);
+ if nfs4_len > 8 {
+ let _nfs4_ts_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8);
+ SCLogDebug!("nfs4_ts_hdr Frame {:?}", _nfs4_ts_hdr);
+ let _nfs4_ts_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8);
+ SCLogDebug!("nfs4_ts_ops Frame {:?}", _nfs4_ts_ops);
+ }
+ }
+
+ fn add_rpc_udp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> {
+ let rpc_udp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8);
+ SCLogDebug!("rpc_tc_pdu frame {:?}", rpc_udp_tc_pdu);
+ rpc_udp_tc_pdu
+ }
+
+ fn add_rpc_udp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) {
+ if rpc_len > 8 {
+ let _rpc_udp_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::RPCHdr as u8);
+ let _rpc_udp_tc_data = Frame::new(flow, stream_slice, &input[8..], rpc_len - 8, NFSFrameType::RPCData as u8);
+ SCLogDebug!("rpc_udp_tc_hdr frame {:?}", _rpc_udp_tc_hdr);
+ SCLogDebug!("rpc_udp_tc_data frame {:?}", _rpc_udp_tc_data);
+ }
+ }
+
+ fn add_rpc_tcp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) -> Option<Frame> {
+ let rpc_tcp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_tcp_len, NFSFrameType::RPCPdu as u8);
+ SCLogDebug!("rpc_tcp_pdu tc frame {:?}", rpc_tcp_tc_pdu);
+ rpc_tcp_tc_pdu
+ }
+
+ fn add_rpc_tcp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) {
+ if rpc_tcp_len > 12 {
+ let _rpc_tcp_tc_hdr = Frame::new(flow, stream_slice, input, 12, NFSFrameType::RPCHdr as u8);
+ let _rpc_tcp_tc_data = Frame::new(flow, stream_slice, &input[12..], rpc_tcp_len - 12, NFSFrameType::RPCData as u8);
+ SCLogDebug!("rpc_tcp_tc_hdr frame {:?}", _rpc_tcp_tc_hdr);
+ SCLogDebug!("rpc_tcp_tc_data frame {:?}", _rpc_tcp_tc_data);
+ }
+ }
+
+ fn add_nfs_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) {
+ if nfs_len > 0 {
+ let _nfs_tc_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8);
+ SCLogDebug!("nfs_tc_pdu frame {:?}", _nfs_tc_pdu);
+ let _nfs_res_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFSStatus as u8);
+ SCLogDebug!("nfs_tc_status frame {:?}", _nfs_res_status);
+ }
+ }
+
+ fn add_nfs4_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) {
+ if nfs4_len > 0 {
+ let _nfs4_tc_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8);
+ SCLogDebug!("nfs4_tc_pdu frame {:?}", _nfs4_tc_pdu);
+ let _nfs4_tc_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFS4Status as u8);
+ SCLogDebug!("nfs4_tc_status frame {:?}", _nfs4_tc_status);
+ }
+ if nfs4_len > 8 {
+ let _nfs4_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8);
+ SCLogDebug!("nfs4_tc_hdr frame {:?}", _nfs4_tc_hdr);
+ let _nfs4_tc_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8);
+ SCLogDebug!("nfs4_tc_ops frame {:?}", _nfs4_tc_ops);
+ }
+ }
+
+ fn post_gap_housekeeping_for_files(&mut self)
+ {
+ let mut post_gap_txs = false;
+ for tx in &mut self.transactions {
+ if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data {
+ if f.post_gap_ts > 0 {
+ if self.ts > f.post_gap_ts {
+ tx.request_done = true;
+ tx.response_done = true;
+ filetracker_trunc(&mut f.file_tracker);
+ } else {
+ post_gap_txs = true;
+ }
+ }
+ }
+ }
+ self.check_post_gap_file_txs = post_gap_txs;
+ }
+
+ /* after a gap we will consider all transactions complete for our
+ * direction. File transfer transactions are an exception. Those
+ * can handle gaps. For the file transactions we set the current
+ * (flow) time and prune them in 60 seconds if no update for them
+ * was received. */
+ fn post_gap_housekeeping(&mut self, dir: Direction)
+ {
+ if self.ts_ssn_gap && dir == Direction::ToServer {
+ for tx in &mut self.transactions {
+ if tx.id >= self.tx_id {
+ SCLogDebug!("post_gap_housekeeping: done");
+ break;
+ }
+ if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data {
+ // leaving FILE txs open as they can deal with gaps. We
+ // remove them after 60 seconds of no activity though.
+ if f.post_gap_ts == 0 {
+ f.post_gap_ts = self.ts + 60;
+ self.check_post_gap_file_txs = true;
+ }
+ } else {
+ SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id);
+ tx.request_done = true;
+ }
+ }
+ } else if self.tc_ssn_gap && dir == Direction::ToClient {
+ for tx in &mut self.transactions {
+ if tx.id >= self.tx_id {
+ SCLogDebug!("post_gap_housekeeping: done");
+ break;
+ }
+ if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data {
+ // leaving FILE txs open as they can deal with gaps. We
+ // remove them after 60 seconds of no activity though.
+ if f.post_gap_ts == 0 {
+ f.post_gap_ts = self.ts + 60;
+ self.check_post_gap_file_txs = true;
+ }
+ } else {
+ SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id);
+ tx.request_done = true;
+ tx.response_done = true;
+ }
+ }
+ }
+ }
+
+ pub fn process_request_record_lookup(&mut self, r: &RpcPacket, xidmap: &mut NFSRequestXidMap) {
+ match parse_nfs3_request_lookup(r.prog_data) {
+ Ok((_, lookup)) => {
+ SCLogDebug!("LOOKUP {:?}", lookup);
+ xidmap.file_name = lookup.name_vec;
+ },
+ _ => {
+ self.set_event(NFSEvent::MalformedData);
+ },
+ };
+ }
+
+ pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) {
+ match self.namemap.get(&xidmap.file_handle) {
+ Some(n) => {
+ SCLogDebug!("xidmap_handle2name: name {:?}", n);
+ xidmap.file_name = n.to_vec();
+ },
+ _ => {
+ SCLogDebug!("xidmap_handle2name: object {:?} not found",
+ xidmap.file_handle);
+ },
+ }
+ }
+
+ /// complete request record
+ fn process_request_record(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcPacket) {
+ SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}",
+ r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+ match r.progver {
+ 4 => {
+ self.add_nfs4_ts_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_request_record_v4(r)
+ },
+ 3 => {
+ self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_request_record_v3(r)
+ },
+ 2 => {
+ self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_request_record_v2(r)
+ },
+ _ => { },
+ }
+ }
+
+ pub fn new_file_tx(&mut self, file_handle: &[u8], file_name: &[u8], direction: Direction)
+ -> &mut NFSTransaction
+ {
+ 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.type_data = Some(NFSTransactionTypeData::FILE(NFSTransactionFile::new()));
+ if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data {
+ d.direction = direction;
+ d.file_tracker.tx_id = tx.id - 1;
+ tx.tx_data.update_file_flags(self.state_data.file_flags);
+ d.update_file_flags(tx.tx_data.file_flags);
+ }
+ tx.tx_data.init_files_opened();
+ tx.tx_data.file_tx = if direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; // TODO direction to flag func?
+ 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();
+ return tx_ref.unwrap();
+ }
+
+ pub fn get_file_tx_by_handle(&mut self, file_handle: &[u8], direction: Direction)
+ -> Option<&mut NFSTransaction>
+ {
+ let fh = file_handle.to_vec();
+ for tx in &mut self.transactions {
+ if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data {
+ if tx.is_file_tx && !tx.is_file_closed &&
+ direction == d.direction &&
+ tx.file_handle == fh
+ {
+ tx.tx_data.update_file_flags(self.state_data.file_flags);
+ d.update_file_flags(tx.tx_data.file_flags);
+ SCLogDebug!("Found NFS file TX with ID {} XID {:04X}", tx.id, tx.xid);
+ return Some(tx);
+ }
+ }
+ }
+ SCLogDebug!("Failed to find NFS TX with handle {:?}", file_handle);
+ return None;
+ }
+
+ pub fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 {
+ let mut fill_bytes = 0;
+ let pad = w.count % 4;
+ if pad != 0 {
+ fill_bytes = 4 - pad;
+ }
+
+ // linux defines a max of 1mb. Allow several multiples.
+ if w.count == 0 || w.count > 16777216 {
+ return 0;
+ }
+
+ // for now assume that stable FILE_SYNC flags means a single chunk
+ let is_last = w.stable == 2;
+ let file_handle = w.handle.value.to_vec();
+ let file_name = if let Some(name) = self.namemap.get(w.handle.value) {
+ SCLogDebug!("WRITE name {:?}", name);
+ name.to_vec()
+ } else {
+ SCLogDebug!("WRITE object {:?} not found", w.handle.value);
+ Vec::new()
+ };
+
+ let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ Some(tx) => {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, w.file_data, w.offset,
+ w.file_len, fill_bytes as u8, is_last, &r.hdr.xid);
+ tdf.chunk_count += 1;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.response_done = true;
+ tx.is_file_closed = true;
+ }
+ true
+ } else {
+ false
+ }
+ },
+ None => { false },
+ };
+ if !found {
+ let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &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;
+ tx.nfs_version = r.progver as u16;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ }
+ if !self.is_udp {
+ self.ts_chunk_xid = r.hdr.xid;
+ debug_validate_bug_on!(w.file_data.len() as u32 > w.count);
+ self.ts_chunk_left = w.count - w.file_data.len() as u32;
+ self.ts_chunk_fh = file_handle;
+ SCLogDebug!("REQUEST chunk_xid {:04X} chunk_left {}", self.ts_chunk_xid, self.ts_chunk_left);
+ }
+ 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());
+
+ let mut xidmap = NFSRequestXidMap::new(r.progver, 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(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcReplyPacket) -> u32 {
+ let mut xidmap;
+ match self.requestmap.remove(&r.hdr.xid) {
+ Some(p) => { xidmap = p; },
+ _ => {
+ SCLogDebug!("REPLY: xid {:04X} NOT FOUND. GAPS? TS:{} TC:{}",
+ r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap);
+
+ // TODO we might be able to try to infer from the size + data
+ // that this is a READ reply and pass the data to the file API anyway?
+ return 0;
+ },
+ }
+ SCLogDebug!("process_reply_record: removed xid {:04X} from requestmap",
+ r.hdr.xid);
+
+ if self.nfs_version == 0 {
+ self.nfs_version = xidmap.progver as u16;
+ }
+
+ match xidmap.progver {
+ 2 => {
+ SCLogDebug!("NFSv2 reply record");
+ self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_reply_record_v2(r, &xidmap);
+ return 0;
+ },
+ 3 => {
+ SCLogDebug!("NFSv3 reply record");
+ self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_reply_record_v3(r, &mut xidmap);
+ return 0;
+ },
+ 4 => {
+ SCLogDebug!("NFSv4 reply record");
+ self.add_nfs4_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64);
+ self.process_reply_record_v4(r, &mut xidmap);
+ return 0;
+ },
+ _ => {
+ SCLogDebug!("Invalid NFS version");
+ self.set_event(NFSEvent::NonExistingVersion);
+ return 0;
+ },
+ }
+ }
+
+ // update in progress chunks for file transfers
+ // return how much data we consumed
+ fn filetracker_update(&mut self, direction: Direction, data: &[u8], gap_size: u32) -> u32 {
+ let mut chunk_left = if direction == Direction::ToServer {
+ self.ts_chunk_left
+ } else {
+ self.tc_chunk_left
+ };
+ if chunk_left == 0 {
+ return 0
+ }
+ let xid = if direction == Direction::ToServer {
+ self.ts_chunk_xid
+ } else {
+ self.tc_chunk_xid
+ };
+ SCLogDebug!("filetracker_update: chunk left {}, input {} chunk_xid {:04X}", chunk_left, data.len(), xid);
+
+ let file_handle;
+ // we have the data that we expect
+ if chunk_left <= data.len() as u32 {
+ chunk_left = 0;
+
+ if direction == Direction::ToServer {
+ self.ts_chunk_xid = 0;
+ file_handle = self.ts_chunk_fh.to_vec();
+ self.ts_chunk_fh.clear();
+ } 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;
+
+ if direction == Direction::ToServer {
+ file_handle = self.ts_chunk_fh.to_vec();
+ } else {
+ // 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 == Direction::ToServer {
+ self.ts_chunk_left = chunk_left;
+ } else {
+ self.tc_chunk_left = chunk_left;
+ }
+
+ let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap;
+ // get the tx and update it
+ let consumed = match self.get_file_tx_by_handle(&file_handle, direction) {
+ Some(tx) => {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ if ssn_gap {
+ let queued_data = tdf.file_tracker.get_queued_size();
+ if queued_data > 2000000 { // TODO should probably be configurable
+ SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data);
+ filetracker_trunc(&mut tdf.file_tracker);
+ }
+ }
+
+ // reset timestamp if we get called after a gap
+ if tdf.post_gap_ts > 0 {
+ tdf.post_gap_ts = 0;
+ }
+
+ tdf.chunk_count += 1;
+ let cs = filetracker_update(&mut tdf.file_tracker, data, gap_size);
+ /* see if we need to close the tx */
+ if tdf.file_tracker.is_done() {
+ if direction == Direction::ToClient {
+ tx.response_done = true;
+ tx.is_file_closed = true;
+ SCLogDebug!("TX {} response is done now that the file track is ready", tx.id);
+ } else {
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ SCLogDebug!("TX {} request is done now that the file track is ready", tx.id);
+ }
+ }
+ cs
+ } else {
+ 0
+ }
+ },
+ 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.
+ pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>,
+ reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32
+ {
+ let file_name;
+ let file_handle;
+ let chunk_offset;
+ let nfs_version;
+
+ let mut fill_bytes = 0;
+ let pad = reply.count % 4;
+ if pad != 0 {
+ fill_bytes = 4 - pad;
+ }
+
+ // linux defines a max of 1mb. Allow several multiples.
+ if reply.count == 0 || reply.count > 16777216 {
+ return 0;
+ }
+
+ match xidmapr {
+ Some(xidmap) => {
+ file_name = xidmap.file_name.to_vec();
+ file_handle = xidmap.file_handle.to_vec();
+ chunk_offset = xidmap.chunk_offset;
+ nfs_version = xidmap.progver;
+ },
+ None => {
+ if let Some(xidmap) = self.requestmap.get(&r.hdr.xid) {
+ file_name = xidmap.file_name.to_vec();
+ file_handle = xidmap.file_handle.to_vec();
+ chunk_offset = xidmap.chunk_offset;
+ nfs_version = xidmap.progver;
+ } else {
+ return 0;
+ }
+ },
+ }
+ SCLogDebug!("chunk_offset {}", chunk_offset);
+
+ let mut is_last = reply.eof;
+ SCLogDebug!("XID {} is_last {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}",
+ r.hdr.xid, is_last, fill_bytes, reply.count, reply.data_len, reply.data.len());
+
+ if nfs_version == 2 {
+ let size = match parse_nfs2_attribs(reply.attr_blob) {
+ Ok((_, ref attr)) => attr.asize,
+ _ => 0,
+ };
+ SCLogDebug!("NFSv2 READ reply record: File size {}. Offset {} data len {}: total {}",
+ size, chunk_offset, reply.data_len, chunk_offset + reply.data_len as u64);
+
+ if size as u64 == chunk_offset + reply.data_len as u64 {
+ is_last = true;
+ }
+
+ }
+
+ let is_partial = reply.data.len() < reply.count as usize;
+ SCLogDebug!("partial data? {}", is_partial);
+
+ let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToClient) {
+ Some(tx) => {
+ SCLogDebug!("updated TX {:?}", tx);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, reply.data, chunk_offset,
+ reply.count, fill_bytes as u8, is_last, &r.hdr.xid);
+ tdf.chunk_count += 1;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.rpc_response_status = r.reply_state;
+ tx.nfs_response_status = reply.status;
+ tx.is_last = true;
+ tx.request_done = true;
+
+ /* if this is a partial record we will close the tx
+ * when we've received the final data */
+ if !is_partial {
+ tx.response_done = true;
+ SCLogDebug!("TX {} is DONE", tx.id);
+ }
+ }
+ true
+ } else {
+ false
+ }
+ },
+ None => { false },
+ };
+ if !found {
+ let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToClient);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, reply.data, chunk_offset,
+ reply.count, fill_bytes as u8, is_last, &r.hdr.xid);
+ tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ };
+ tx.xid = r.hdr.xid;
+ tx.is_first = true;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.rpc_response_status = r.reply_state;
+ tx.nfs_response_status = reply.status;
+ tx.is_last = true;
+ tx.request_done = true;
+
+ /* if this is a partial record we will close the tx
+ * when we've received the final data */
+ if !is_partial {
+ tx.response_done = true;
+ SCLogDebug!("TX {} is DONE", tx.id);
+ }
+ }
+ }
+ }
+
+ if !self.is_udp {
+ self.tc_chunk_xid = r.hdr.xid;
+ debug_validate_bug_on!(reply.data.len() as u32 > reply.count);
+ self.tc_chunk_left = reply.count - reply.data.len() as u32;
+ }
+
+ SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {} chunk_xid {:04X}",
+ r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left,
+ self.tc_chunk_xid);
+ 0
+ }
+
+ fn process_partial_read_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &NfsReplyRead<'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 {
+ if let Some(xidmap) = self.requestmap.get(&r.xid) {
+ return xidmap.procedure;
+ } else {
+ SCLogDebug!("REPLY: xid {} NOT FOUND", r.xid);
+ return 0;
+ }
+ }
+
+ pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> AppLayerResult {
+ SCLogDebug!("parse_tcp_data_ts_gap ({})", gap_size);
+ let gap = vec![0; gap_size as usize];
+ let consumed = self.filetracker_update(Direction::ToServer, &gap, gap_size);
+ if consumed > gap_size {
+ SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size);
+ return AppLayerResult::ok();
+ }
+ self.ts_ssn_gap = true;
+ self.ts_gap = true;
+ SCLogDebug!("parse_tcp_data_ts_gap ({}) done", gap_size);
+ return AppLayerResult::ok();
+ }
+
+ pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> AppLayerResult {
+ SCLogDebug!("parse_tcp_data_tc_gap ({})", gap_size);
+ let gap = vec![0; gap_size as usize];
+ let consumed = self.filetracker_update(Direction::ToClient, &gap, gap_size);
+ if consumed > gap_size {
+ SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size);
+ return AppLayerResult::ok();
+ }
+ self.tc_ssn_gap = true;
+ self.tc_gap = true;
+ SCLogDebug!("parse_tcp_data_tc_gap ({}) done", gap_size);
+ return AppLayerResult::ok();
+ }
+
+ /// Handle partial records
+ fn parse_tcp_partial_data_ts<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8],
+ phdr: &RpcRequestPacketPartial, rec_size: usize) -> AppLayerResult {
+ // 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 we in WRITE mode?
+ if 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, false) {
+ Ok((_rem, ref hdr)) => {
+ // we got here because rec_size > input, so we should never have
+ // remaining data
+ debug_validate_bug_on!(!_rem.is_empty());
+
+ match parse_nfs3_request_write(hdr.prog_data, false) {
+ Ok((_, ref w)) => {
+ // deal with the partial nfs write data
+ self.process_partial_write_request_record(hdr, w);
+ return AppLayerResult::ok();
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ Err(Err::Incomplete(_)) => {
+ // this is normal, fall through to incomplete handling
+ }
+ }
+ }
+ Err(Err::Incomplete(_)) => {
+ // size check was done for a minimal RPC record size,
+ // so Incomplete is normal.
+ SCLogDebug!("TS data incomplete");
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ }
+ }
+ }
+ // make sure we pass a value higher than current input
+ // but lower than the record size
+ let n1 = cmp::max(cur_i.len(), 1024);
+ let n2 = cmp::min(n1, rec_size);
+ return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32);
+ }
+
+ /// Parsing function, handling TCP chunks fragmentation
+ pub fn parse_tcp_data_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult {
+ let mut cur_i = stream_slice.as_slice();
+ // take care of in progress file chunk transfers
+ // and skip buffer beyond it
+ let consumed = self.filetracker_update(Direction::ToServer, cur_i, 0);
+ if consumed > 0 {
+ if consumed > cur_i.len() as u32 {
+ return AppLayerResult::err();
+ }
+ cur_i = &cur_i[consumed as usize..];
+ }
+ if cur_i.is_empty() {
+ return AppLayerResult::ok();
+ }
+ if self.ts_gap {
+ SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len());
+
+ let mut _cnt = 0;
+ while !cur_i.is_empty() {
+ _cnt += 1;
+ match nfs_probe(cur_i, Direction::ToServer) {
+ 1 => {
+ SCLogDebug!("expected data found");
+ self.ts_gap = false;
+ break;
+ },
+ 0 => {
+ SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.",
+ cur_i.len(), _cnt);
+ return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32);
+ },
+ -1 => {
+ cur_i = &cur_i[1..];
+ if cur_i.is_empty() {
+ SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt);
+ }
+ },
+ _ => {
+ return AppLayerResult::err();
+ },
+ }
+ }
+ SCLogDebug!("TS GAP handling done (input {})", cur_i.len());
+ }
+
+ while !cur_i.is_empty() { // min record size
+ self.add_rpc_tcp_ts_pdu(flow, stream_slice, cur_i, cur_i.len() as i64);
+ match parse_rpc_request_partial(cur_i) {
+ Ok((_, ref rpc_phdr)) => {
+ let rec_size = (rpc_phdr.hdr.frag_len + 4) as usize;
+
+ // Handle partial records
+ if rec_size > cur_i.len() {
+ return self.parse_tcp_partial_data_ts(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size);
+ }
+
+ // we have the full records size worth of data,
+ // let's parse it. Errors lead to event, but are
+ // not fatal as we already have enough info to
+ // go to the next record.
+ match parse_rpc(cur_i, true) {
+ Ok((_, ref rpc_record)) => {
+ self.add_rpc_tcp_ts_creds(flow, stream_slice, &cur_i[RPC_TCP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64);
+ self.process_request_record(flow, stream_slice, rpc_record);
+ }
+ Err(Err::Incomplete(_)) => {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ }
+ }
+ cur_i = &cur_i[rec_size..];
+ }
+ Err(Err::Incomplete(needed)) => {
+ if let Needed::Size(n) = needed {
+ SCLogDebug!("Not enough data for partial RPC header {:?}", needed);
+ // 28 is the partial RPC header size parse_rpc_request_partial
+ // looks for.
+ let n = usize::from(n);
+ let need = if n > 28 { n } else { 28 };
+ return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32);
+ }
+ return AppLayerResult::err();
+ }
+ /* This error is fatal. If we failed to parse the RPC hdr we don't
+ * have a length and we don't know where the next record starts. */
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ }
+ };
+
+ self.post_gap_housekeeping(Direction::ToServer);
+ if self.check_post_gap_file_txs && !self.post_gap_files_checked {
+ self.post_gap_housekeeping_for_files();
+ self.post_gap_files_checked = true;
+ }
+
+ AppLayerResult::ok()
+ }
+
+ /// Handle partial records
+ fn parse_tcp_partial_data_tc<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8],
+ phdr: &RpcPacketHeader, rec_size: usize) -> AppLayerResult {
+ // 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(phdr) == 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, false) {
+ Ok((_rem, ref hdr)) => {
+ // we got here because rec_size > input, so we should never have
+ // remaining data
+ debug_validate_bug_on!(!_rem.is_empty());
+
+ match parse_nfs3_reply_read(hdr.prog_data, false) {
+ Ok((_, ref r)) => {
+ // deal with the partial nfs read data
+ self.process_partial_read_reply_record(hdr, r);
+ return AppLayerResult::ok();
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ Err(Err::Incomplete(_)) => {
+ // this is normal, fall through to incomplete handling
+ }
+ }
+ }
+ Err(Err::Incomplete(_)) => {
+ // size check was done for a minimal RPC record size,
+ // so Incomplete is normal.
+ SCLogDebug!("TC data incomplete");
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ }
+ }
+ }
+ // make sure we pass a value higher than current input
+ // but lower than the record size
+ let n1 = cmp::max(cur_i.len(), 1024);
+ let n2 = cmp::min(n1, rec_size);
+ return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32);
+ }
+
+ /// Parsing function, handling TCP chunks fragmentation
+ pub fn parse_tcp_data_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult {
+ let mut cur_i = stream_slice.as_slice();
+ // take care of in progress file chunk transfers
+ // and skip buffer beyond it
+ let consumed = self.filetracker_update(Direction::ToClient, cur_i, 0);
+ if consumed > 0 {
+ if consumed > cur_i.len() as u32 {
+ return AppLayerResult::err();
+ }
+ cur_i = &cur_i[consumed as usize..];
+ }
+ if cur_i.is_empty() {
+ return AppLayerResult::ok();
+ }
+ if self.tc_gap {
+ SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len());
+
+ let mut _cnt = 0;
+ while !cur_i.is_empty() {
+ _cnt += 1;
+ match nfs_probe(cur_i, Direction::ToClient) {
+ 1 => {
+ SCLogDebug!("expected data found");
+ self.tc_gap = false;
+ break;
+ },
+ 0 => {
+ SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.",
+ cur_i.len(), _cnt);
+ return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32);
+ },
+ -1 => {
+ cur_i = &cur_i[1..];
+ if cur_i.is_empty() {
+ SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt);
+ }
+ },
+ _ => {
+ return AppLayerResult::err();
+ }
+ }
+ }
+ SCLogDebug!("TC GAP handling done (input {})", cur_i.len());
+ }
+
+ while !cur_i.is_empty() {
+ self.add_rpc_tcp_tc_pdu(flow, stream_slice, cur_i, cur_i.len() as i64);
+ match parse_rpc_packet_header(cur_i) {
+ Ok((_, ref rpc_phdr)) => {
+ let rec_size = (rpc_phdr.frag_len + 4) as usize;
+ // see if we have all data available
+ if rec_size > cur_i.len() {
+ return self.parse_tcp_partial_data_tc(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size);
+ }
+
+ // we have the full data of the record, lets parse
+ match parse_rpc_reply(cur_i, true) {
+ Ok((_, ref rpc_record)) => {
+ self.add_rpc_tcp_tc_frames(flow, stream_slice, cur_i, cur_i.len() as i64);
+ self.process_reply_record(flow, stream_slice, rpc_record);
+ }
+ Err(Err::Incomplete(_)) => {
+ // we shouldn't get incomplete as we have the full data
+ // so if we got incomplete anyway it's the data that is
+ // bad.
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ }
+ }
+ cur_i = &cur_i[rec_size..]; // progress input past parsed record
+ }
+ Err(Err::Incomplete(needed)) => {
+ if let Needed::Size(n) = needed {
+ SCLogDebug!("Not enough data for partial RPC header {:?}", needed);
+ // 12 is the partial RPC header size parse_rpc_packet_header
+ // looks for.
+ let n = usize::from(n);
+ let need = if n > 12 { n } else { 12 };
+ return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32);
+ }
+ return AppLayerResult::err();
+ }
+ /* This error is fatal. If we failed to parse the RPC hdr we don't
+ * have a length and we don't know where the next record starts. */
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ self.set_event(NFSEvent::MalformedData);
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ return AppLayerResult::err();
+ }
+ }
+ };
+ self.post_gap_housekeeping(Direction::ToClient);
+ if self.check_post_gap_file_txs && !self.post_gap_files_checked {
+ self.post_gap_housekeeping_for_files();
+ self.post_gap_files_checked = true;
+ }
+ AppLayerResult::ok()
+ }
+ /// Parsing function
+ pub fn parse_udp_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult {
+ let input = stream_slice.as_slice();
+ SCLogDebug!("parse_udp_ts ({})", input.len());
+ self.add_rpc_udp_ts_pdu(flow, stream_slice, input, input.len() as i64);
+ if !input.is_empty() {
+ match parse_rpc_udp_request(input) {
+ Ok((_, ref rpc_record)) => {
+ self.is_udp = true;
+ self.add_rpc_udp_ts_creds(flow, stream_slice, &input[RPC_UDP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64);
+ match rpc_record.progver {
+ 3 => {
+ self.process_request_record(flow, stream_slice, rpc_record);
+ },
+ 2 => {
+ self.add_nfs_ts_frame(flow, stream_slice, rpc_record.prog_data, rpc_record.prog_data_size as i64);
+ self.process_request_record_v2(rpc_record);
+ },
+ _ => { },
+ }
+ },
+ Err(Err::Incomplete(_)) => {
+ },
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ }
+ }
+ }
+ AppLayerResult::ok()
+ }
+
+ /// Parsing function
+ pub fn parse_udp_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult {
+ let input = stream_slice.as_slice();
+ SCLogDebug!("parse_udp_tc ({})", input.len());
+ self.add_rpc_udp_tc_pdu(flow, stream_slice, input, input.len() as i64);
+ if !input.is_empty() {
+ match parse_rpc_udp_reply(input) {
+ Ok((_, ref rpc_record)) => {
+ self.is_udp = true;
+ self.add_rpc_udp_tc_frames(flow, stream_slice, input, input.len() as i64);
+ self.process_reply_record(flow, stream_slice, rpc_record);
+ },
+ Err(Err::Incomplete(_)) => {
+ },
+ Err(Err::Error(_e)) |
+ Err(Err::Failure(_e)) => {
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ }
+ }
+ }
+ AppLayerResult::ok()
+ }
+}
+
+/// Returns *mut NFSState
+#[no_mangle]
+pub extern "C" fn rs_nfs_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void {
+ let state = NFSState::new();
+ let boxed = Box::new(state);
+ SCLogDebug!("allocating state");
+ return Box::into_raw(boxed) as *mut _;
+}
+
+/// Params:
+/// - state: *mut NFSState as void pointer
+#[no_mangle]
+pub extern "C" fn rs_nfs_state_free(state: *mut std::os::raw::c_void) {
+ // Just unbox...
+ SCLogDebug!("freeing state");
+ std::mem::drop(unsafe { Box::from_raw(state as *mut NFSState) });
+}
+
+/// C binding parse a NFS TCP request. Returns 1 on success, -1 on failure.
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_parse_request(flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+ ) -> AppLayerResult
+{
+ let state = cast_pointer!(state, NFSState);
+ let flow = cast_pointer!(flow, Flow);
+
+ if stream_slice.is_gap() {
+ return rs_nfs_parse_request_tcp_gap(state, stream_slice.gap_size());
+ }
+ SCLogDebug!("parsing {} bytes of request data", stream_slice.len());
+
+ state.update_ts(flow.get_last_time().as_secs());
+ state.parse_tcp_data_ts(flow, &stream_slice)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs_parse_request_tcp_gap(
+ state: &mut NFSState,
+ input_len: u32)
+ -> AppLayerResult
+{
+ state.parse_tcp_data_ts_gap(input_len)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_parse_response(flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+ ) -> AppLayerResult
+{
+ let state = cast_pointer!(state, NFSState);
+ let flow = cast_pointer!(flow, Flow);
+
+ if stream_slice.is_gap() {
+ return rs_nfs_parse_response_tcp_gap(state, stream_slice.gap_size());
+ }
+ SCLogDebug!("parsing {} bytes of response data", stream_slice.len());
+
+ state.update_ts(flow.get_last_time().as_secs());
+ state.parse_tcp_data_tc(flow, &stream_slice)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_nfs_parse_response_tcp_gap(
+ state: &mut NFSState,
+ input_len: u32)
+ -> AppLayerResult
+{
+ state.parse_tcp_data_tc_gap(input_len)
+}
+
+/// C binding to parse an NFS/UDP request. Returns 1 on success, -1 on failure.
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_parse_request_udp(f: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+ ) -> AppLayerResult
+{
+ let state = cast_pointer!(state, NFSState);
+
+ SCLogDebug!("parsing {} bytes of request data", stream_slice.len());
+ state.parse_udp_ts(f, &stream_slice)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_parse_response_udp(f: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+ ) -> AppLayerResult
+{
+ let state = cast_pointer!(state, NFSState);
+ SCLogDebug!("parsing {} bytes of response data", stream_slice.len());
+ state.parse_udp_tc(f, &stream_slice)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_state_get_tx_count(state: *mut std::os::raw::c_void)
+ -> u64
+{
+ let state = cast_pointer!(state, NFSState);
+ SCLogDebug!("rs_nfs_state_get_tx_count: returning {}", state.tx_id);
+ return state.tx_id;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_state_get_tx(state: *mut std::os::raw::c_void,
+ tx_id: u64)
+ -> *mut std::os::raw::c_void
+{
+ let state = cast_pointer!(state, NFSState);
+ match state.get_tx_by_id(tx_id) {
+ Some(tx) => {
+ return tx as *const _ as *mut _;
+ }
+ None => {
+ return std::ptr::null_mut();
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_state_tx_free(state: *mut std::os::raw::c_void,
+ tx_id: u64)
+{
+ let state = cast_pointer!(state, NFSState);
+ state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_tx_get_alstate_progress(tx: *mut std::os::raw::c_void,
+ direction: u8)
+ -> std::os::raw::c_int
+{
+ let tx = cast_pointer!(tx, NFSTransaction);
+ if direction == Direction::ToServer.into() && tx.request_done {
+ SCLogDebug!("TOSERVER progress 1");
+ return 1;
+ } else if direction == Direction::ToClient.into() && tx.response_done {
+ SCLogDebug!("TOCLIENT progress 1");
+ return 1;
+ } else {
+ SCLogDebug!("{} progress 0", direction);
+ return 0;
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_get_tx_data(
+ tx: *mut std::os::raw::c_void)
+ -> *mut AppLayerTxData
+{
+ let tx = cast_pointer!(tx, NFSTransaction);
+ return &mut tx.tx_data;
+}
+
+export_state_data_get!(rs_nfs_get_state_data, NFSState);
+
+/// 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 unsafe extern "C" fn rs_nfs_tx_get_procedures(tx: &mut NFSTransaction,
+ i: u16,
+ procedure: *mut u32)
+ -> u8
+{
+ if i == 0 {
+ *procedure = tx.procedure;
+ return 1;
+ }
+
+ if !tx.is_file_tx {
+ return 0;
+ }
+
+ /* file tx handling follows */
+
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ let idx = i as usize - 1;
+ if idx < tdf.file_additional_procs.len() {
+ let p = tdf.file_additional_procs[idx];
+ *procedure = p;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_tx_get_version(tx: &mut NFSTransaction,
+ version: *mut u32)
+{
+ *version = tx.nfs_version as u32;
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_init(context: &'static mut SuricataFileContext)
+{
+ SURICATA_NFS_FILE_CONFIG = Some(context);
+}
+
+fn nfs_probe_dir(i: &[u8], rdir: *mut u8) -> i8 {
+ match parse_rpc_packet_header(i) {
+ Ok((_, ref hdr)) => {
+ let dir = if hdr.msgtype == 0 {
+ Direction::ToServer
+ } else {
+ Direction::ToClient
+ };
+ unsafe { *rdir = dir as u8 };
+ return 1;
+ },
+ Err(Err::Incomplete(_)) => {
+ return 0;
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+}
+
+pub fn nfs_probe(i: &[u8], direction: Direction) -> i32 {
+ if direction == Direction::ToClient {
+ match parse_rpc_reply(i, false) {
+ Ok((_, ref rpc)) => {
+ if rpc.hdr.frag_len >= 24 && rpc.hdr.frag_len <= 35000 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 {
+ SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
+ return 1;
+ } else {
+ return -1;
+ }
+ },
+ Err(Err::Incomplete(_)) => {
+ match parse_rpc_packet_header (i) {
+ Ok((_, ref rpc_hdr)) => {
+ if rpc_hdr.frag_len >= 24 && rpc_hdr.frag_len <= 35000 && rpc_hdr.xid != 0 && rpc_hdr.msgtype == 1 {
+ SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype);
+ return 1;
+ } else {
+ return -1;
+ }
+ },
+ Err(Err::Incomplete(_)) => {
+ return 0;
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+ } else {
+ match parse_rpc(i, false) {
+ Ok((_, ref rpc)) => {
+ if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 &&
+ rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) &&
+ rpc.program == 100003 &&
+ rpc.procedure <= NFSPROC3_COMMIT
+ {
+ return rpc_auth_type_known(rpc.creds_flavor) as i32;
+ } else {
+ return -1;
+ }
+ },
+ Err(Err::Incomplete(_)) => {
+ return 0;
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+ }
+}
+
+pub fn nfs_probe_udp(i: &[u8], direction: Direction) -> i32 {
+ if direction == Direction::ToClient {
+ match parse_rpc_udp_reply(i) {
+ Ok((_, ref rpc)) => {
+ if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 {
+ SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype);
+ return 1;
+ } else {
+ return -1;
+ }
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+ } else {
+ match parse_rpc_udp_request(i) {
+ Ok((_, ref rpc)) => {
+ if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 {
+ return 1;
+ } else if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 2 && rpc.program == 100003 {
+ SCLogDebug!("NFSv2!");
+ return 1;
+ } else {
+ return -1;
+ }
+ },
+ Err(_) => {
+ return -1;
+ },
+ }
+ }
+}
+
+/// MIDSTREAM
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_probe_ms(
+ _flow: *const Flow,
+ direction: u8, input: *const u8,
+ len: u32, rdir: *mut u8) -> AppProto
+{
+ let slice: &[u8] = build_slice!(input, len as usize);
+ SCLogDebug!("rs_nfs_probe_ms: probing direction {:02x}", direction);
+ let mut adirection : u8 = 0;
+ match nfs_probe_dir(slice, &mut adirection) {
+ 1 => {
+ if adirection == Direction::ToServer.into() {
+ SCLogDebug!("nfs_probe_dir said Direction::ToServer");
+ } else {
+ SCLogDebug!("nfs_probe_dir said Direction::ToClient");
+ }
+ match nfs_probe(slice, adirection.into()) {
+ 1 => {
+ SCLogDebug!("nfs_probe success: dir {:02x} adir {:02x}", direction, adirection);
+ if (direction & DIR_BOTH) != adirection {
+ *rdir = adirection;
+ }
+ ALPROTO_NFS
+ },
+ 0 => { ALPROTO_UNKNOWN },
+ _ => { ALPROTO_FAILED },
+ }
+ },
+ 0 => {
+ ALPROTO_UNKNOWN
+ },
+ _ => {
+ ALPROTO_FAILED
+ }
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_probe(_f: *const Flow,
+ direction: u8,
+ input: *const u8,
+ len: u32,
+ _rdir: *mut u8)
+ -> AppProto
+{
+ let slice: &[u8] = build_slice!(input, len as usize);
+ SCLogDebug!("rs_nfs_probe: running probe");
+ match nfs_probe(slice, direction.into()) {
+ 1 => { ALPROTO_NFS },
+ -1 => { ALPROTO_FAILED },
+ _ => { ALPROTO_UNKNOWN },
+ }
+}
+
+/// TOSERVER probe function
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_probe_udp_ts(_f: *const Flow,
+ _direction: u8,
+ input: *const u8,
+ len: u32,
+ _rdir: *mut u8)
+ -> AppProto
+{
+ let slice: &[u8] = build_slice!(input, len as usize);
+ match nfs_probe_udp(slice, Direction::ToServer) {
+ 1 => { ALPROTO_NFS },
+ -1 => { ALPROTO_FAILED },
+ _ => { ALPROTO_UNKNOWN },
+ }
+}
+
+/// TOCLIENT probe function
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_probe_udp_tc(_f: *const Flow,
+ _direction: u8,
+ input: *const u8,
+ len: u32,
+ _rdir: *mut u8)
+ -> AppProto
+{
+ let slice: &[u8] = build_slice!(input, len as usize);
+ match nfs_probe_udp(slice, Direction::ToClient) {
+ 1 => { ALPROTO_NFS },
+ -1 => { ALPROTO_FAILED },
+ _ => { ALPROTO_UNKNOWN },
+ }
+}
+
+// Parser name as a C style string.
+const PARSER_NAME: &[u8] = b"nfs\0";
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_register_parser() {
+ let default_port = CString::new("[2049]").unwrap();
+ let parser = RustParser {
+ name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+ default_port: std::ptr::null(),
+ ipproto: IPPROTO_TCP,
+ probe_ts: None,
+ probe_tc: None,
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_nfs_state_new,
+ state_free: rs_nfs_state_free,
+ tx_free: rs_nfs_state_tx_free,
+ parse_ts: rs_nfs_parse_request,
+ parse_tc: rs_nfs_parse_response,
+ get_tx_count: rs_nfs_state_get_tx_count,
+ get_tx: rs_nfs_state_get_tx,
+ tx_comp_st_ts: 1,
+ tx_comp_st_tc: 1,
+ tx_get_progress: rs_nfs_tx_get_alstate_progress,
+ get_eventinfo: Some(NFSEvent::get_event_info),
+ get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_files: Some(rs_nfs_gettxfiles),
+ get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>),
+ get_tx_data: rs_nfs_get_tx_data,
+ get_state_data: rs_nfs_get_state_data,
+ apply_tx_config: None,
+ flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS,
+ truncate: None,
+ get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name),
+ get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id),
+ };
+
+ let ip_proto_str = CString::new("tcp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(
+ ip_proto_str.as_ptr(),
+ parser.name,
+ ) != 0
+ {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_NFS = alproto;
+
+ let midstream = conf_get_bool("stream.midstream");
+ if midstream {
+ if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_TCP,
+ parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN,
+ rs_nfs_probe_ms, rs_nfs_probe_ms) == 0 {
+ SCLogDebug!("No NFSTCP app-layer configuration, enabling NFSTCP detection TCP detection on port {:?}.",
+ default_port);
+ /* register 'midstream' probing parsers if midstream is enabled. */
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP,
+ default_port.as_ptr(), ALPROTO_NFS, 0,
+ NFS_MIN_FRAME_LEN, Direction::ToServer.into(),
+ rs_nfs_probe_ms, rs_nfs_probe_ms);
+ }
+ } else {
+ AppLayerProtoDetectPPRegister(IPPROTO_TCP,
+ default_port.as_ptr(), ALPROTO_NFS, 0,
+ NFS_MIN_FRAME_LEN, Direction::ToServer.into(),
+ rs_nfs_probe, rs_nfs_probe);
+ }
+ if AppLayerParserConfParserEnabled(
+ ip_proto_str.as_ptr(),
+ parser.name,
+ ) != 0
+ {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ SCLogDebug!("Rust nfs parser registered.");
+ } else {
+ SCLogDebug!("Protocol detector and parser disabled for nfs.");
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_nfs_udp_register_parser() {
+ let default_port = CString::new("[2049]").unwrap();
+ let parser = RustParser {
+ name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+ default_port: std::ptr::null(),
+ ipproto: IPPROTO_UDP,
+ probe_ts: None,
+ probe_tc: None,
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_nfs_state_new,
+ state_free: rs_nfs_state_free,
+ tx_free: rs_nfs_state_tx_free,
+ parse_ts: rs_nfs_parse_request_udp,
+ parse_tc: rs_nfs_parse_response_udp,
+ get_tx_count: rs_nfs_state_get_tx_count,
+ get_tx: rs_nfs_state_get_tx,
+ tx_comp_st_ts: 1,
+ tx_comp_st_tc: 1,
+ tx_get_progress: rs_nfs_tx_get_alstate_progress,
+ get_eventinfo: Some(NFSEvent::get_event_info),
+ get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_files: Some(rs_nfs_gettxfiles),
+ get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>),
+ get_tx_data: rs_nfs_get_tx_data,
+ get_state_data: rs_nfs_get_state_data,
+ apply_tx_config: None,
+ flags: 0,
+ truncate: None,
+ get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name),
+ get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id),
+ };
+
+ let ip_proto_str = CString::new("udp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(
+ ip_proto_str.as_ptr(),
+ parser.name,
+ ) != 0
+ {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_NFS = alproto;
+
+ if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_UDP,
+ parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN,
+ rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc) == 0 {
+ SCLogDebug!("No NFSUDP app-layer configuration, enabling NFSUDP detection UDP detection on port {:?}.",
+ default_port);
+ AppLayerProtoDetectPPRegister(IPPROTO_UDP,
+ default_port.as_ptr(), ALPROTO_NFS, 0,
+ NFS_MIN_FRAME_LEN, Direction::ToServer.into(),
+ rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc);
+ }
+ if AppLayerParserConfParserEnabled(
+ ip_proto_str.as_ptr(),
+ parser.name,
+ ) != 0
+ {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ if let Some(val) = conf_get("app-layer.protocols.nfs.max-tx") {
+ if let Ok(v) = val.parse::<usize>() {
+ NFS_MAX_TX = v;
+ } else {
+ SCLogError!("Invalid value for nfs.max-tx");
+ }
+ }
+ SCLogDebug!("Rust nfs parser registered.");
+ } else {
+ SCLogDebug!("Protocol detector and parser disabled for nfs.");
+ }
+}
diff --git a/rust/src/nfs/nfs2.rs b/rust/src/nfs/nfs2.rs
new file mode 100644
index 0000000..f8000b4
--- /dev/null
+++ b/rust/src/nfs/nfs2.rs
@@ -0,0 +1,122 @@
+/* Copyright (C) 2017-2020 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
+
+use crate::nfs::nfs::*;
+use crate::nfs::types::*;
+use crate::nfs::rpc_records::*;
+use crate::nfs::nfs2_records::*;
+
+use nom7::IResult;
+use nom7::number::streaming::be_u32;
+
+impl NFSState {
+ /// complete request record
+ pub fn process_request_record_v2(&mut self, r: &RpcPacket) {
+ SCLogDebug!("NFSv2: REQUEST {} procedure {} ({}) blob size {}",
+ r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+ let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
+ let aux_file_name = Vec::new();
+
+ if r.procedure == NFSPROC3_LOOKUP {
+ match parse_nfs2_request_lookup(r.prog_data) {
+ Ok((_, ar)) => {
+ xidmap.file_handle = ar.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ },
+ _ => {
+ self.set_event(NFSEvent::MalformedData);
+ },
+ };
+ } else if r.procedure == NFSPROC3_READ {
+ match parse_nfs2_request_read(r.prog_data) {
+ Ok((_, read_record)) => {
+ xidmap.chunk_offset = read_record.offset as u64;
+ xidmap.file_handle = read_record.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ },
+ _ => {
+ self.set_event(NFSEvent::MalformedData);
+ },
+ };
+ }
+
+ 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();
+ tx.file_handle = xidmap.file_handle.to_vec();
+ tx.nfs_version = r.progver as u16;
+
+ if r.procedure == NFSPROC3_RENAME {
+ tx.type_data = Some(NFSTransactionTypeData::RENAME(aux_file_name));
+ }
+
+ tx.auth_type = r.creds_flavor;
+ #[allow(clippy::single_match)]
+ match r.creds {
+ RpcRequestCreds::Unix(ref u) => {
+ tx.request_machine_name = u.machine_name_buf.to_vec();
+ tx.request_uid = u.uid;
+ tx.request_gid = u.gid;
+ },
+ _ => { },
+ }
+ SCLogDebug!("NFSv2: TX created: ID {} XID {} PROCEDURE {}",
+ tx.id, tx.xid, tx.procedure);
+ self.transactions.push(tx);
+ }
+
+ SCLogDebug!("NFSv2: TS creating xidmap {}", r.hdr.xid);
+ self.requestmap.insert(r.hdr.xid, xidmap);
+ }
+
+ pub fn process_reply_record_v2(&mut self, r: &RpcReplyPacket, xidmap: &NFSRequestXidMap) {
+ let mut nfs_status = 0;
+ let resp_handle = Vec::new();
+
+ if xidmap.procedure == NFSPROC3_READ {
+ match parse_nfs2_reply_read(r.prog_data) {
+ Ok((_, ref reply)) => {
+ SCLogDebug!("NFSv2: READ reply record");
+ self.process_read_record(r, reply, Some(xidmap));
+ nfs_status = reply.status;
+ },
+ _ => {
+ self.set_event(NFSEvent::MalformedData);
+ },
+ }
+ } else {
+ let stat : u32 = match be_u32(r.prog_data) as IResult<&[u8],_> {
+ Ok((_, stat)) => stat,
+ _ => 0
+ };
+ nfs_status = stat;
+ }
+ SCLogDebug!("NFSv2: REPLY {} to procedure {} blob size {}",
+ r.hdr.xid, xidmap.procedure, r.prog_data.len());
+
+ self.mark_response_tx_done(r.hdr.xid, r.reply_state, nfs_status, &resp_handle);
+ }
+}
diff --git a/rust/src/nfs/nfs2_records.rs b/rust/src/nfs/nfs2_records.rs
new file mode 100644
index 0000000..d8fe84f
--- /dev/null
+++ b/rust/src/nfs/nfs2_records.rs
@@ -0,0 +1,235 @@
+/* Copyright (C) 2017-2020 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 NFSv2 records
+
+use crate::nfs::nfs_records::*;
+use nom7::bytes::streaming::take;
+use nom7::combinator::{rest, cond};
+use nom7::number::streaming::be_u32;
+use nom7::IResult;
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs2Handle<'a> {
+ pub value: &'a[u8],
+}
+
+pub fn parse_nfs2_handle(i: &[u8]) -> IResult<&[u8], Nfs2Handle> {
+ let (i, value) = take(32_usize)(i)?;
+ Ok((i, Nfs2Handle { value }))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs2RequestLookup<'a> {
+ pub handle: Nfs2Handle<'a>,
+ pub name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs2_request_lookup(i: &[u8]) -> IResult<&[u8], Nfs2RequestLookup> {
+ let (i, handle) = parse_nfs2_handle(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name_contents) = take(name_len as usize)(i)?;
+ let (i, _name_padding) = rest(i)?;
+ let req = Nfs2RequestLookup {
+ handle,
+ name_vec: name_contents.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs2RequestRead<'a> {
+ pub handle: Nfs2Handle<'a>,
+ pub offset: u32,
+}
+
+pub fn parse_nfs2_request_read(i: &[u8]) -> IResult<&[u8], Nfs2RequestRead> {
+ let (i, handle) = parse_nfs2_handle(i)?;
+ let (i, offset) = be_u32(i)?;
+ let (i, _count) = be_u32(i)?;
+ let req = Nfs2RequestRead { handle, offset };
+ Ok((i, req))
+}
+
+pub fn parse_nfs2_reply_read(i: &[u8]) -> IResult<&[u8], NfsReplyRead> {
+ let (i, status) = be_u32(i)?;
+ let (i, attr_blob) = take(68_usize)(i)?;
+ let (i, data_len) = be_u32(i)?;
+ let (i, data_contents) = take(data_len)(i)?;
+ let fill_bytes = 4 - (data_len % 4);
+ let (i, _) = cond(fill_bytes != 0, take(fill_bytes))(i)?;
+ let reply = NfsReplyRead {
+ status,
+ attr_follows: 1,
+ attr_blob,
+ count: data_len,
+ eof: false,
+ data_len,
+ data: data_contents,
+ };
+ Ok((i, reply))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs2Attributes<> {
+ pub atype: u32,
+ pub asize: u32,
+}
+
+pub fn parse_nfs2_attribs(i: &[u8]) -> IResult<&[u8], Nfs2Attributes> {
+ let (i, atype) = be_u32(i)?;
+ let (i, _blob1) = take(16_usize)(i)?;
+ let (i, asize) = be_u32(i)?;
+ let (i, _blob2) = take(44_usize)(i)?;
+ let attrs = Nfs2Attributes { atype, asize };
+ Ok((i, attrs))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::nfs::nfs2_records::*;
+
+ #[test]
+ fn test_nfs2_handle() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29
+ ];
+
+ let result = parse_nfs2_handle(buf).unwrap();
+ match result {
+ (r, res) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(res.value, buf);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs2_request_lookup() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x02, 0x61, 0x6d, 0x00, 0x00, /*name*/
+ ];
+
+ let (_, handle) = parse_nfs2_handle(buf).unwrap();
+ assert_eq!(handle.value, &buf[..32]);
+
+ let result = parse_nfs2_request_lookup(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, handle);
+ assert_eq!(request.name_vec, b"am".to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs2_request_read() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d,
+ 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x00, /*offset*/
+ 0x00, 0x00, 0x20, 0x00, /*count*/
+ 0x00, 0x00, 0x20, 0x00, /*_total_count*/
+ ];
+
+ let (_, handle) = parse_nfs2_handle(buf).unwrap();
+ assert_eq!(handle.value, &buf[..32]);
+
+ let result = parse_nfs2_request_read(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 4);
+ assert_eq!(request.handle, handle);
+ assert_eq!(request.offset, 0);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs2_reply_read() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*Status: NFS_OK - (0)*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x81, 0xa4, /*attr_blob*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x10, 0x85,
+ 0x00, 0x00, 0xb2, 0x5d, 0x38, 0x47, 0x75, 0xea,
+ 0x00, 0x0b, 0x71, 0xb0, 0x38, 0x47, 0x71, 0xc4,
+ 0x00, 0x08, 0xb2, 0x90, 0x38, 0x47, 0x75, 0xea,
+ 0x00, 0x09, 0x00, 0xb0,
+ 0x00, 0x00, 0x00, 0x0b, /*data_len*/
+ 0x74, 0x68, 0x65, 0x20, 0x62, 0x20, 0x66, 0x69, /*data_contents: ("the b file")*/
+ 0x6c, 0x65, 0x0a,
+ 0x00, /*_data_padding*/
+ ];
+
+ let result = parse_nfs2_reply_read(buf).unwrap();
+ match result {
+ (r, response) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(response.status, 0);
+ assert_eq!(response.attr_follows, 1);
+ assert_eq!(response.attr_blob.len(), 68);
+ assert_eq!(response.count, response.data_len);
+ assert!(!response.eof);
+ assert_eq!(response.data_len, 11);
+ assert_eq!(response.data, &buf[76..87]);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs2_attributes() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x01, /*Type: Regular File (1)*/
+ 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x00, 0x01, /*attr: _blob1*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, /*size: 0*/
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /*attr: _blob2*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x85,
+ 0x00, 0x00, 0xa3, 0xe7, 0x38, 0x47, 0x75, 0xea,
+ 0x00, 0x08, 0x16, 0x50, 0x38, 0x47, 0x75, 0xea,
+ 0x00, 0x08, 0x16, 0x50, 0x38, 0x47, 0x75, 0xea,
+ 0x00, 0x08, 0x16, 0x50
+ ];
+
+ let result = parse_nfs2_attribs(buf).unwrap();
+ match result {
+ (r, res) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(res.atype, 1);
+ assert_eq!(res.asize, 0);
+ }
+ }
+ }
+}
diff --git a/rust/src/nfs/nfs3.rs b/rust/src/nfs/nfs3.rs
new file mode 100644
index 0000000..032751f
--- /dev/null
+++ b/rust/src/nfs/nfs3.rs
@@ -0,0 +1,274 @@
+/* Copyright (C) 2017-2020 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
+
+use crate::core::*;
+
+use crate::nfs::nfs::*;
+use crate::nfs::types::*;
+use crate::nfs::rpc_records::*;
+use crate::nfs::nfs3_records::*;
+
+use nom7::IResult;
+use nom7::number::streaming::be_u32;
+
+impl NFSState {
+ /// complete NFS3 request record
+ pub fn process_request_record_v3(&mut self, r: &RpcPacket) {
+ SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}",
+ r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len());
+
+ let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
+ let mut aux_file_name = Vec::new();
+
+ if self.nfs_version == 0 {
+ self.nfs_version = r.progver as u16;
+ }
+
+ if r.procedure == NFSPROC3_LOOKUP {
+ self.process_request_record_lookup(r, &mut xidmap);
+
+ } else if r.procedure == NFSPROC3_ACCESS {
+ if let Ok((_, rd)) = parse_nfs3_request_access(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_GETATTR {
+ if let Ok((_, rd)) = parse_nfs3_request_getattr(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_READDIRPLUS {
+ if let Ok((_, rd)) = parse_nfs3_request_readdirplus(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_READ {
+ if let Ok((_, rd)) = parse_nfs3_request_read(r.prog_data) {
+ xidmap.chunk_offset = rd.offset;
+ xidmap.file_handle = rd.handle.value.to_vec();
+ self.xidmap_handle2name(&mut xidmap);
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_WRITE {
+ if let Ok((_, rd)) = parse_nfs3_request_write(r.prog_data, true) {
+ self.process_write_record(r, &rd);
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ } else if r.procedure == NFSPROC3_CREATE {
+ if let Ok((_, rd)) = parse_nfs3_request_create(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ xidmap.file_name = rd.name_vec;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_REMOVE {
+ if let Ok((_, rd)) = parse_nfs3_request_remove(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ xidmap.file_name = rd.name_vec;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_RENAME {
+ if let Ok((_, rd)) = parse_nfs3_request_rename(r.prog_data) {
+ xidmap.file_handle = rd.from_handle.value.to_vec();
+ xidmap.file_name = rd.from_name_vec;
+ aux_file_name = rd.to_name_vec;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_MKDIR {
+ if let Ok((_, rd)) = parse_nfs3_request_mkdir(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ xidmap.file_name = rd.name_vec;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_RMDIR {
+ if let Ok((_, rd)) = parse_nfs3_request_rmdir(r.prog_data) {
+ xidmap.file_handle = rd.handle.value.to_vec();
+ xidmap.file_name = rd.name_vec;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if r.procedure == NFSPROC3_COMMIT {
+ SCLogDebug!("COMMIT, closing shop");
+ if let Ok((_, rd)) = parse_nfs3_request_commit(r.prog_data) {
+ let file_handle = rd.handle.value.to_vec();
+ if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ tdf.chunk_count += 1;
+ tdf.file_additional_procs.push(NFSPROC3_COMMIT);
+ filetracker_close(&mut tdf.file_tracker);
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ }
+
+ 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();
+ tx.nfs_version = r.progver as u16;
+ tx.file_handle = xidmap.file_handle.to_vec();
+
+ if r.procedure == NFSPROC3_RENAME {
+ tx.type_data = Some(NFSTransactionTypeData::RENAME(aux_file_name));
+ }
+
+ tx.auth_type = r.creds_flavor;
+ #[allow(clippy::single_match)]
+ match r.creds {
+ RpcRequestCreds::Unix(ref u) => {
+ tx.request_machine_name = u.machine_name_buf.to_vec();
+ tx.request_uid = u.uid;
+ tx.request_gid = u.gid;
+ },
+ _ => { },
+ }
+ SCLogDebug!("TX created: ID {} XID {} PROCEDURE {}",
+ tx.id, tx.xid, tx.procedure);
+ self.transactions.push(tx);
+
+ } else if r.procedure == NFSPROC3_READ {
+
+ let found = self.get_file_tx_by_handle(&xidmap.file_handle, Direction::ToClient).is_some();
+ if !found {
+ let tx = self.new_file_tx(&xidmap.file_handle, &xidmap.file_name, Direction::ToClient);
+ tx.procedure = NFSPROC3_READ;
+ tx.xid = r.hdr.xid;
+ tx.is_first = true;
+ tx.nfs_version = r.progver as u16;
+
+ tx.auth_type = r.creds_flavor;
+ if let RpcRequestCreds::Unix(ref u) = r.creds {
+ tx.request_machine_name = u.machine_name_buf.to_vec();
+ tx.request_uid = u.uid;
+ tx.request_gid = u.gid;
+ }
+ }
+ }
+
+ self.requestmap.insert(r.hdr.xid, xidmap);
+ }
+
+ pub fn process_reply_record_v3(&mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap) {
+ let mut nfs_status = 0;
+ let mut resp_handle = Vec::new();
+
+ if xidmap.procedure == NFSPROC3_LOOKUP {
+ if let Ok((_, rd)) = parse_nfs3_response_lookup(r.prog_data) {
+ SCLogDebug!("LOOKUP: {:?}", rd);
+ SCLogDebug!("RESPONSE LOOKUP file_name {:?}", xidmap.file_name);
+
+ nfs_status = rd.status;
+
+ SCLogDebug!("LOOKUP handle {:?}", rd.handle);
+ self.namemap.insert(rd.handle.value.to_vec(), xidmap.file_name.to_vec());
+ resp_handle = rd.handle.value.to_vec();
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if xidmap.procedure == NFSPROC3_CREATE {
+ if let Ok((_, rd)) = parse_nfs3_response_create(r.prog_data) {
+ SCLogDebug!("nfs3_create_record: {:?}", rd);
+ SCLogDebug!("RESPONSE CREATE file_name {:?}", xidmap.file_name);
+ nfs_status = rd.status;
+
+ if let Some(h) = rd.handle {
+ SCLogDebug!("handle {:?}", h);
+ self.namemap.insert(h.value.to_vec(), xidmap.file_name.to_vec());
+ resp_handle = h.value.to_vec();
+ }
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ };
+ } else if xidmap.procedure == NFSPROC3_READ {
+ if let Ok((_, rd)) = parse_nfs3_reply_read(r.prog_data, true) {
+ self.process_read_record(r, &rd, Some(xidmap));
+ nfs_status = rd.status;
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ } else if xidmap.procedure == NFSPROC3_READDIRPLUS {
+ if let Ok((_, rd)) = parse_nfs3_response_readdirplus(r.prog_data) {
+ nfs_status = rd.status;
+
+ // cut off final eof field
+ let d = if rd.data.len() >= 4 {
+ &rd.data[..rd.data.len()-4_usize]
+ } else {
+ rd.data
+ };
+
+ // store all handle/filename mappings
+ if let Ok((_, ref entries)) = many0_nfs3_response_readdirplus_entries(d) {
+ SCLogDebug!("READDIRPLUS ENTRIES reply {:?}", entries);
+ for ce in entries {
+ SCLogDebug!("ce {:?}", ce);
+ if let Some(ref e) = ce.entry {
+ SCLogDebug!("e {:?}", e);
+ if let Some(ref h) = e.handle {
+ SCLogDebug!("h {:?}", h);
+ self.namemap.insert(h.value.to_vec(),
+ e.name_vec.to_vec());
+ }
+ }
+ }
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ } else {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ }
+ // for all other record types only parse the status
+ else {
+ let stat : u32 = match be_u32(r.prog_data) as IResult<&[u8],_> {
+ Ok((_, stat)) => stat,
+ _ => 0
+ };
+ nfs_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, r.reply_state, nfs_status, &resp_handle);
+ }
+ }
+}
diff --git a/rust/src/nfs/nfs3_records.rs b/rust/src/nfs/nfs3_records.rs
new file mode 100644
index 0000000..952b367
--- /dev/null
+++ b/rust/src/nfs/nfs3_records.rs
@@ -0,0 +1,1000 @@
+/* Copyright (C) 2017-2022 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 std::cmp;
+use crate::nfs::nfs_records::*;
+use nom7::bytes::streaming::take;
+use nom7::combinator::{complete, cond, rest, verify};
+use nom7::multi::{length_data, many0};
+use nom7::number::streaming::{be_u32, be_u64};
+use nom7::IResult;
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3Handle<'a> {
+ pub len: u32,
+ pub value: &'a [u8],
+}
+
+pub fn parse_nfs3_handle(i: &[u8]) -> IResult<&[u8], Nfs3Handle> {
+ let (i, len) = be_u32(i)?;
+ let (i, value) = take(len as usize)(i)?;
+ let handle = Nfs3Handle { len, value };
+ Ok((i, handle))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3ReplyCreate<'a> {
+ pub status: u32,
+ pub handle: Option<Nfs3Handle<'a>>,
+}
+
+pub fn parse_nfs3_response_create(i: &[u8]) -> IResult<&[u8], Nfs3ReplyCreate> {
+ let (i, status) = be_u32(i)?;
+ let (i, handle_has_value) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, handle) = cond(handle_has_value == 1, parse_nfs3_handle)(i)?;
+ let reply = Nfs3ReplyCreate { status, handle };
+ Ok((i, reply))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3ReplyLookup<'a> {
+ pub status: u32,
+ pub handle: Nfs3Handle<'a>,
+}
+
+pub fn parse_nfs3_response_lookup(i: &[u8]) -> IResult<&[u8], Nfs3ReplyLookup> {
+ let (i, status) = be_u32(i)?;
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let reply = Nfs3ReplyLookup { status, handle };
+ Ok((i, reply))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+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>,
+}
+
+pub fn parse_nfs3_request_create(i: &[u8]) -> IResult<&[u8], Nfs3RequestCreate> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name) = take(name_len as usize)(i)?;
+ let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?;
+ let (i, create_mode) = be_u32(i)?;
+ let (i, verifier) = rest(i)?;
+ let req = Nfs3RequestCreate {
+ handle,
+ name_len,
+ create_mode,
+ verifier,
+ name_vec: name.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestRemove<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub name_len: u32,
+ pub name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs3_request_remove(i: &[u8]) -> IResult<&[u8], Nfs3RequestRemove> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name) = take(name_len as usize)(i)?;
+ let (i, _fill_bytes) = rest(i)?;
+ let req = Nfs3RequestRemove {
+ handle,
+ name_len,
+ name_vec: name.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestRmdir<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs3_request_rmdir(i: &[u8]) -> IResult<&[u8], Nfs3RequestRmdir> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name) = take(name_len as usize)(i)?;
+ let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?;
+ let req = Nfs3RequestRmdir {
+ handle,
+ name_vec: name.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestMkdir<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs3_request_mkdir(i: &[u8]) -> IResult<&[u8], Nfs3RequestMkdir> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name) = take(name_len as usize)(i)?;
+ let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?;
+ let (i, _attributes) = rest(i)?;
+ let req = Nfs3RequestMkdir {
+ handle,
+ name_vec: name.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestRename<'a> {
+ pub from_handle: Nfs3Handle<'a>,
+ pub from_name_vec: Vec<u8>,
+ pub to_handle: Nfs3Handle<'a>,
+ pub to_name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs3_request_rename(i: &[u8]) -> IResult<&[u8], Nfs3RequestRename> {
+ let (i, from_handle) = parse_nfs3_handle(i)?;
+ let (i, from_name_len) = be_u32(i)?;
+ let (i, from_name) = take(from_name_len as usize)(i)?;
+ let (i, _from_fill_bytes) = cond(from_name_len % 4 != 0, take(4 - (from_name_len % 4)))(i)?;
+
+ let (i, to_handle) = parse_nfs3_handle(i)?;
+ let (i, to_name_len) = be_u32(i)?;
+ let (i, to_name) = take(to_name_len as usize)(i)?;
+ let (i, _from_fill_bytes) = rest(i)?;
+ let req = Nfs3RequestRename {
+ from_handle,
+ from_name_vec: from_name.to_vec(),
+ to_handle,
+ to_name_vec: to_name.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestGetAttr<'a> {
+ pub handle: Nfs3Handle<'a>,
+}
+
+pub fn parse_nfs3_request_getattr(i: &[u8]) -> IResult<&[u8], Nfs3RequestGetAttr> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ Ok((i, Nfs3RequestGetAttr { handle }))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestAccess<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub check_access: u32,
+}
+
+pub fn parse_nfs3_request_access(i: &[u8]) -> IResult<&[u8], Nfs3RequestAccess> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, check_access) = be_u32(i)?;
+ let req = Nfs3RequestAccess {
+ handle,
+ check_access,
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestCommit<'a> {
+ pub handle: Nfs3Handle<'a>,
+}
+
+pub fn parse_nfs3_request_commit(i: &[u8]) -> IResult<&[u8], Nfs3RequestCommit> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, _offset) = be_u64(i)?;
+ let (i, _count) = be_u32(i)?;
+ Ok((i, Nfs3RequestCommit { handle }))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestRead<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub offset: u64,
+}
+
+pub fn parse_nfs3_request_read(i: &[u8]) -> IResult<&[u8], Nfs3RequestRead> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, offset) = be_u64(i)?;
+ let (i, _count) = be_u32(i)?;
+ Ok((i, Nfs3RequestRead { handle, offset }))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestLookup<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub name_vec: Vec<u8>,
+}
+
+pub fn parse_nfs3_request_lookup(i: &[u8]) -> IResult<&[u8], Nfs3RequestLookup> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, name_contents) = length_data(be_u32)(i)?;
+ let (i, _name_padding) = rest(i)?;
+ let req = Nfs3RequestLookup {
+ handle,
+ name_vec: name_contents.to_vec(),
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3ResponseReaddirplusEntryC<'a> {
+ pub name_vec: Vec<u8>,
+ pub handle: Option<Nfs3Handle<'a>>,
+}
+
+pub fn parse_nfs3_response_readdirplus_entry(
+ i: &[u8],
+) -> IResult<&[u8], Nfs3ResponseReaddirplusEntryC> {
+ let (i, _file_id) = be_u64(i)?;
+ let (i, name_len) = be_u32(i)?;
+ let (i, name_contents) = take(name_len as usize)(i)?;
+ let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?;
+ let (i, _cookie) = take(8_usize)(i)?;
+ let (i, attr_value_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, _attr) = cond(attr_value_follows == 1, take(84_usize))(i)?;
+ let (i, handle_value_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, handle) = cond(handle_value_follows == 1, parse_nfs3_handle)(i)?;
+ let resp = Nfs3ResponseReaddirplusEntryC {
+ name_vec: name_contents.to_vec(),
+ handle,
+ };
+ Ok((i, resp))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3ResponseReaddirplusEntry<'a> {
+ pub entry: Option<Nfs3ResponseReaddirplusEntryC<'a>>,
+}
+
+pub fn parse_nfs3_response_readdirplus_entry_cond(
+ i: &[u8],
+) -> IResult<&[u8], Nfs3ResponseReaddirplusEntry> {
+ let (i, value_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, entry) = cond(value_follows == 1, parse_nfs3_response_readdirplus_entry)(i)?;
+ Ok((i, Nfs3ResponseReaddirplusEntry { entry }))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3ResponseReaddirplus<'a> {
+ pub status: u32,
+ pub data: &'a [u8],
+}
+
+pub fn parse_nfs3_response_readdirplus(i: &[u8]) -> IResult<&[u8], Nfs3ResponseReaddirplus> {
+ let (i, status) = be_u32(i)?;
+ let (i, dir_attr_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, _dir_attr) = cond(dir_attr_follows == 1, take(84_usize))(i)?;
+ let (i, _verifier) = be_u64(i)?;
+ let (i, data) = rest(i)?;
+ let resp = Nfs3ResponseReaddirplus { status, data };
+ Ok((i, resp))
+}
+
+pub(crate) fn many0_nfs3_response_readdirplus_entries(
+ input: &[u8],
+) -> IResult<&[u8], Vec<Nfs3ResponseReaddirplusEntry>> {
+ many0(complete(parse_nfs3_response_readdirplus_entry_cond))(input)
+}
+
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs3RequestReaddirplus<'a> {
+ pub handle: Nfs3Handle<'a>,
+ pub cookie: u64,
+ pub verifier: &'a [u8],
+ pub dircount: u32,
+ pub maxcount: u32,
+}
+
+pub fn parse_nfs3_request_readdirplus(i: &[u8]) -> IResult<&[u8], Nfs3RequestReaddirplus> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, cookie) = be_u64(i)?;
+ let (i, verifier) = take(8_usize)(i)?;
+ let (i, dircount) = be_u32(i)?;
+ let (i, maxcount) = be_u32(i)?;
+ let req = Nfs3RequestReaddirplus {
+ handle,
+ cookie,
+ verifier,
+ dircount,
+ maxcount,
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+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],
+}
+
+/// Complete data expected
+fn parse_nfs3_data_complete(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> {
+ let (i, file_data) = take(file_len)(i)?;
+ let (i, _) = cond(fill_bytes > 0, take(fill_bytes))(i)?;
+ Ok((i, file_data))
+}
+
+/// Partial data. We have all file_len, but need to consider fill_bytes
+fn parse_nfs3_data_partial(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> {
+ let (i, file_data) = take(file_len)(i)?;
+ let fill_bytes = cmp::min(fill_bytes, i.len());
+ let (i, _) = cond(fill_bytes > 0, take(fill_bytes))(i)?;
+ Ok((i, file_data))
+}
+
+/// Parse WRITE record. Consider 3 cases:
+/// 1. we have the complete RPC data
+/// 2. we have incomplete data but enough for all file data (partial fill bytes)
+/// 3. we have incomplete file data
+pub fn parse_nfs3_request_write(i: &[u8], complete: bool) -> IResult<&[u8], Nfs3RequestWrite> {
+ let (i, handle) = parse_nfs3_handle(i)?;
+ let (i, offset) = be_u64(i)?;
+ let (i, count) = be_u32(i)?;
+ let (i, stable) = verify(be_u32, |&v| v <= 2)(i)?;
+ let (i, file_len) = verify(be_u32, |&v| v <= count)(i)?;
+ let fill_bytes = if file_len % 4 != 0 { 4 - file_len % 4 } else { 0 };
+ // Handle the various file data parsing logics
+ let (i, file_data) = if complete {
+ parse_nfs3_data_complete(i, file_len as usize, fill_bytes as usize)?
+ } else if i.len() >= file_len as usize {
+ parse_nfs3_data_partial(i, file_len as usize, fill_bytes as usize)?
+ } else {
+ rest(i)?
+ };
+ let req = Nfs3RequestWrite {
+ handle,
+ offset,
+ count,
+ stable,
+ file_len,
+ file_data,
+ };
+ Ok((i, req))
+}
+
+pub fn parse_nfs3_reply_read(i: &[u8], complete: bool) -> IResult<&[u8], NfsReplyRead> {
+ let (i, status) = be_u32(i)?;
+ let (i, attr_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, attr_blob) = take(84_usize)(i)?; // fixed size?
+ let (i, count) = be_u32(i)?;
+ let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, data_len) = verify(be_u32, |&v| v <= count)(i)?;
+ let fill_bytes = if data_len % 4 != 0 { 4 - data_len % 4 } else { 0 };
+ // Handle the various file data parsing logics
+ let (i, data) = if complete {
+ parse_nfs3_data_complete(i, data_len as usize, fill_bytes as usize)?
+ } else if i.len() >= data_len as usize {
+ parse_nfs3_data_partial(i, data_len as usize, fill_bytes as usize)?
+ } else {
+ rest(i)?
+ };
+ let reply = NfsReplyRead {
+ status,
+ attr_follows,
+ attr_blob,
+ count,
+ eof: eof != 0,
+ data_len,
+ data,
+ };
+ Ok((i, reply))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::nfs::nfs3_records::*;
+
+ #[test]
+ fn test_nfs3_response_create() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/
+ 0x00, 0x00, 0x00, 0x01, /*handle_follows: (1)*/
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54,
+ 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[8..]).unwrap();
+
+ let (_, response) = parse_nfs3_response_create(buf).unwrap();
+ assert_eq!(response.status, 0);
+ assert_eq!(response.handle, Some(expected_handle));
+ }
+
+ #[test]
+ fn test_nfs3_response_lookup() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xec,
+ 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[4..]).unwrap();
+
+ let (_, response) = parse_nfs3_response_lookup(buf).unwrap();
+ assert_eq!(response.status, 0);
+ assert_eq!(response.handle, expected_handle);
+ }
+
+ #[test]
+ fn test_nfs3_request_create() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xe7,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [name]
+ 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/
+ 0x68, /*name_contents: (h)*/
+ 0x00, 0x00, 0x00, /*_fill_bytes*/
+ 0x00, 0x00, 0x00, 0x00, /*create_mode: UNCHECKED (0)*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xa4, /*verifier*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_create(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.name_len, 1);
+ assert_eq!(request.create_mode, 0);
+ assert_eq!(request.verifier.len(), 44);
+ assert_eq!(request.name_vec, br#"h"#.to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_remove() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xe7,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [name]
+ 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/
+ 0x68, /*name_contents: (h)*/
+ 0x00, 0x00, 0x00, /*_fill_bytes*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_remove(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.name_len, 1);
+ assert_eq!(request.name_vec, br#"h"#.to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_rmdir() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ //[handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [name]
+ 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/
+ 0x64, /*name_contents: (d)*/
+ 0x00, 0x00, 0x00, /*_fill_bytes*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_rmdir(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.name_vec, br#"d"#.to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_mkdir() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [name]
+ 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/
+ 0x64, /*name_contents: (d)*/
+ 0x00, 0x00, 0x00, /*_fill_bytes*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xed, /*attributes*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_mkdir(buf).unwrap();
+ match result {
+ (_r, request) => {
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.name_vec, br#"d"#.to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_rename() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [from_handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [from_name]
+ 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/
+ 0x61, /*name: (a)*/
+ 0x00, 0x00, 0x00, /*_fill_bytes*/
+ // [to_handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [to_name]
+ 0x00, 0x00, 0x00, 0x02, /*name_len: (2)*/
+ 0x61, 0x6d, /*name: (am)*/
+ 0x00, 0x00, /*_fill_bytes*/
+ ];
+
+ let (_, expected_from_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+ let (_, expected_to_handle) = parse_nfs3_handle(&buf[44..80]).unwrap();
+
+ let result = parse_nfs3_request_rename(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+
+ assert_eq!(request.from_handle, expected_from_handle);
+ assert_eq!(request.from_name_vec, br#"a"#.to_vec());
+
+ assert_eq!(request.to_handle, expected_to_handle);
+ assert_eq!(request.to_name_vec, br#"am"#.to_vec());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_getattr() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(buf).unwrap();
+
+ let result = parse_nfs3_request_getattr(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_access() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54,
+ 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x0c, /*check_access: (12)*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_access(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.check_access, 12);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_commit() {
+
+ // packet_bytes -- used [READ Call] message digest
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d,
+ 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/
+ 0x00, 0x00, 0x40, 0x00, /*count:*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_commit(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_read() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d,
+ 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/
+ 0x00, 0x00, 0x40, 0x00, /*_count*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_read(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.offset, 0);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_lookup() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a,
+ 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ // [name]
+ 0x00, 0x00, 0x00, 0x03, /*name_len: (3)*/
+ 0x62, 0x6c, 0x6e, /*name: (bln)*/
+ 0x00, /*_fill_bytes*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_lookup(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.name_vec, br#"bln"#);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_response_readdirplus() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x01, /*dir_attr_follows*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x41, 0xc0, /*_dir_attr*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x63, 0xc3, 0x9d, 0x1a,
+ 0x66, 0xf3, 0x85, 0x5e, 0x00, 0x00, 0x00, 0x00,
+ 0x0c, 0x66, 0x00, 0x03, 0x59, 0x39, 0x5a, 0x3a,
+ 0x18, 0x13, 0x9c, 0xb2, 0x55, 0xe1, 0x59, 0xd4,
+ 0x0e, 0xa0, 0xc0, 0x41, 0x55, 0xe1, 0x59, 0xd4,
+ 0x0e, 0xa0, 0xc0, 0x41,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/
+ // [data]
+ 0x00, 0x00, 0x00, 0x01, /*value_follows*/
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x01, /*entry0*/
+ 0x00, 0x00, 0x00, 0x02, 0x2e, 0x2e, 0x00, 0x00, /*name_contents: \2e\2e */
+ 0x00, 0x00, 0x00, 0x00, 0x7c, 0x1b, 0xc6, 0xaf,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x05,
+ 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x01,
+ 0x59, 0x38, 0xa1, 0xa1, 0x04, 0x29, 0xd9, 0x59,
+ 0x4e, 0xbf, 0xf1, 0x51, 0x09, 0x2c, 0xa1, 0xda,
+ 0x4e, 0xbf, 0xf1, 0x51, 0x09, 0x2c, 0xa1, 0xda,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24,
+ 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/
+ 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66,
+ 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x66, 0x0c,
+ 0x4a, 0xff, 0x6b, 0x99,
+ 0x00, 0x00, 0x00, 0x01, /*value_follows*/
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, /*entry1*/
+ 0x00, 0x00, 0x00, 0x01, 0x2e, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03,
+ 0x59, 0x39, 0x5a, 0x3a, 0x18, 0x13, 0x9c, 0xb2,
+ 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41,
+ 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24,
+ 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/
+ 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66,
+ 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c,
+ 0x4c, 0xff, 0x6b, 0x99, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01,
+ ];
+
+ let data_buf = &buf[100..];
+
+ let (_, response) = parse_nfs3_response_readdirplus(buf).unwrap();
+ assert_eq!(response.status, 0);
+ assert_eq!(response.data, data_buf);
+
+ // test for multiple entries
+ let entry0_buf = &data_buf[4..160];
+ let entry1_buf = &data_buf[164..320];
+
+ let (_, entry0) = parse_nfs3_response_readdirplus_entry(entry0_buf).unwrap();
+ let (_, entry1) = parse_nfs3_response_readdirplus_entry(entry1_buf).unwrap();
+
+ let response = many0_nfs3_response_readdirplus_entries(data_buf).unwrap();
+ match response {
+ (r, entries) => {
+ assert_eq!(r.len(), 4);
+ assert_eq!(entries[0], Nfs3ResponseReaddirplusEntry { entry: Some(entry0) });
+ assert_eq!(entries[1], Nfs3ResponseReaddirplusEntry { entry: Some(entry1) });
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_response_readdirplus_entry() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x01, /*value_follows*/
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, /*entry*/
+ 0x00, 0x00, 0x00, 0x01, 0x2e, 0x00, 0x00, 0x00, /*name_contents: 2e*/
+ 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03,
+ 0x59, 0x39, 0x5a, 0x3a, 0x18, 0x13, 0x9c, 0xb2,
+ 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41,
+ 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24,
+ 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/
+ 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66,
+ 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c,
+ 0x4c, 0xff, 0x6b, 0x99,
+ ];
+
+ let (_, entry_handle) = parse_nfs3_handle(&buf[120..]).unwrap();
+ assert_eq!(entry_handle.len, 36);
+ assert_eq!(entry_handle.value, &buf[124..]);
+
+ let (_, response) = parse_nfs3_response_readdirplus_entry_cond(buf).unwrap();
+ match response {
+ Nfs3ResponseReaddirplusEntry { entry: Some(entry_c) } => {
+ assert_eq!(entry_c.name_vec, ".".as_bytes());
+ assert_eq!(entry_c.handle, Some(entry_handle));
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_readdirplus() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x24, /*handle_len*/
+ 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/
+ 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66,
+ 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c,
+ 0x4c, 0xff, 0x6b, 0x99,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*cookie*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*verifier*/
+ 0x00, 0x00, 0x02, 0x00, /*dircount*/
+ 0x00, 0x00, 0x10, 0x00, /*maxcount*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..40]).unwrap();
+ assert_eq!(expected_handle.len, 36);
+ assert_eq!(expected_handle.value, &buf[4..40]);
+
+ let result = parse_nfs3_request_readdirplus(buf).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.cookie, 0);
+ assert_eq!(request.verifier, "\0\0\0\0\0\0\0\0".as_bytes());
+ assert_eq!(request.verifier.len(), 8);
+ assert_eq!(request.dircount, 512);
+ assert_eq!(request.maxcount, 4096);
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_request_write() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ // [handle]
+ 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/
+ 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54,
+ 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/
+ 0x00, 0x00, 0x00, 0x11, /*count: (17)*/
+ 0x00, 0x00, 0x00, 0x01, /*stable: <DATA_SYNC> (1)*/
+ // [data]
+ 0x00, 0x00, 0x00, 0x11, /*file_len: (17)*/
+ 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x0a, 0x74, 0x68, /*file_data: ("hallo\nthe b file\n")*/
+ 0x65, 0x20, 0x62, 0x20, 0x66, 0x69, 0x6c, 0x65,
+ 0x0a,
+ 0x00, 0x00, 0x00, /*_data_padding*/
+ ];
+
+ let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap();
+
+ let result = parse_nfs3_request_write(buf, true).unwrap();
+ match result {
+ (r, request) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(request.handle, expected_handle);
+ assert_eq!(request.offset, 0);
+ assert_eq!(request.count, 17);
+ assert_eq!(request.stable, 1);
+ assert_eq!(request.file_len, 17);
+ assert_eq!(request.file_data, "hallo\nthe b file\n".as_bytes());
+ }
+ }
+ }
+
+ #[test]
+ fn test_nfs3_reply_read() {
+
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/
+ 0x00, 0x00, 0x00, 0x01, /*attributes_follows: (1)*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x81, 0xa4, /*attr_blob*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0xb2, 0x5d, 0x38, 0x47, 0x76, 0x25,
+ 0x23, 0xc3, 0x46, 0x00, 0x38, 0x47, 0x71, 0xc4,
+ 0x21, 0xf9, 0x82, 0x80, 0x38, 0x47, 0x76, 0x25,
+ 0x1e, 0x65, 0xfb, 0x81,
+ 0x00, 0x00, 0x00, 0x0b, /*count: (11)*/
+ 0x00, 0x00, 0x00, 0x01, /*EOF: (true)*/
+ // [data]
+ 0x00, 0x00, 0x00, 0x0b, /*data_len: (11)*/
+ 0x74, 0x68, 0x65, 0x20, 0x62, 0x20, 0x66, 0x69,
+ 0x6c, 0x65, 0x0a, /*data: ("the b file\n")*/
+ 0x00, /*_data_padding*/
+ ];
+
+ let result = parse_nfs3_reply_read(buf, true).unwrap();
+ match result {
+ (r, reply) => {
+ assert_eq!(r.len(), 0);
+ assert_eq!(reply.status, 0);
+ assert_eq!(reply.attr_follows, 1);
+ assert_eq!(reply.attr_blob.len(), 84);
+ assert_eq!(reply.count, 11);
+ assert!(reply.eof);
+ assert_eq!(reply.data_len, 11);
+ assert_eq!(reply.data, "the b file\n".as_bytes());
+ }
+ }
+ }
+}
diff --git a/rust/src/nfs/nfs4.rs b/rust/src/nfs/nfs4.rs
new file mode 100644
index 0000000..730e82b
--- /dev/null
+++ b/rust/src/nfs/nfs4.rs
@@ -0,0 +1,425 @@
+/* Copyright (C) 2018-2020 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
+
+use nom7::bytes::streaming::take;
+use nom7::number::streaming::be_u32;
+use nom7::{Err, IResult};
+
+use crate::core::*;
+use crate::nfs::nfs::*;
+use crate::nfs::nfs4_records::*;
+use crate::nfs::nfs_records::*;
+use crate::nfs::rpc_records::*;
+use crate::nfs::types::*;
+
+use crate::kerberos::{parse_kerberos5_request, Kerberos5Ticket, SecBlobError};
+
+fn parse_req_gssapi(i: &[u8]) -> IResult<&[u8], Kerberos5Ticket, SecBlobError> {
+ let (i, len) = be_u32(i)?;
+ let (i, buf) = take(len as usize)(i)?;
+ let (_, ap) = parse_kerberos5_request(buf)?;
+ Ok((i, ap))
+}
+
+impl NFSState {
+ /* normal write: PUTFH (file handle), WRITE (write opts/data). File handle
+ * is not part of the write record itself so we pass it in here. */
+ fn write_v4<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs4RequestWrite<'b>, fh: &'b [u8]) {
+ // for now assume that stable FILE_SYNC flags means a single chunk
+ let is_last = w.stable == 2;
+ SCLogDebug!("is_last {}", is_last);
+
+ let mut fill_bytes = 0;
+ let pad = w.write_len % 4;
+ if pad != 0 {
+ fill_bytes = 4 - pad;
+ }
+
+ // linux defines a max of 1mb. Allow several multiples.
+ if w.write_len == 0 || w.write_len > 16777216 {
+ return;
+ }
+
+ let file_handle = fh.to_vec();
+ let file_name = if let Some(name) = self.namemap.get(fh) {
+ SCLogDebug!("WRITE name {:?}", name);
+ name.to_vec()
+ } else {
+ SCLogDebug!("WRITE object {:?} not found", w.stateid.data);
+ Vec::new()
+ };
+
+ let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ Some(tx) => {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, w.data, w.offset,
+ w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+ tdf.chunk_count += 1;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.response_done = true;
+ }
+ }
+ true
+ }
+ None => false,
+ };
+ if !found {
+ let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer);
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_newchunk(&mut tdf.file_tracker,
+ &file_name, w.data, w.offset,
+ w.write_len, fill_bytes as u8, is_last, &r.hdr.xid);
+ tx.procedure = NFSPROC4_WRITE;
+ tx.xid = r.hdr.xid;
+ tx.is_first = true;
+ tx.nfs_version = r.progver as u16;
+ if is_last {
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ }
+ self.ts_chunk_xid = r.hdr.xid;
+ debug_validate_bug_on!(w.data.len() as u32 > w.write_len);
+ self.ts_chunk_left = w.write_len - w.data.len() as u32;
+ }
+
+ fn close_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
+ self.commit_v4(r, fh)
+ }
+
+ fn commit_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) {
+ SCLogDebug!("COMMIT, closing shop");
+
+ let file_handle = fh.to_vec();
+ if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) {
+ if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data {
+ filetracker_close(&mut tdf.file_tracker);
+ tdf.file_last_xid = r.hdr.xid;
+ tx.is_last = true;
+ tx.request_done = true;
+ tx.is_file_closed = true;
+ }
+ }
+ }
+
+ fn new_tx_v4(
+ &mut self, r: &RpcPacket, xidmap: &NFSRequestXidMap, procedure: u32,
+ _aux_opcodes: &[u32],
+ ) {
+ let mut tx = self.new_tx();
+ tx.xid = r.hdr.xid;
+ tx.procedure = procedure;
+ tx.request_done = true;
+ tx.file_name = xidmap.file_name.to_vec();
+ tx.nfs_version = r.progver as u16;
+ tx.file_handle = xidmap.file_handle.to_vec();
+
+ tx.auth_type = r.creds_flavor;
+ #[allow(clippy::single_match)]
+ match r.creds {
+ RpcRequestCreds::Unix(ref u) => {
+ tx.request_machine_name = u.machine_name_buf.to_vec();
+ tx.request_uid = u.uid;
+ tx.request_gid = u.gid;
+ }
+ _ => {}
+ }
+ SCLogDebug!(
+ "NFSv4: TX created: ID {} XID {} PROCEDURE {}",
+ tx.id,
+ tx.xid,
+ tx.procedure
+ );
+ self.transactions.push(tx);
+ }
+
+ /* A normal READ request looks like: PUTFH (file handle) READ (read opts).
+ * We need the file handle for the READ.
+ */
+ fn compound_request<'b>(
+ &mut self, r: &RpcPacket<'b>, cr: &Nfs4RequestCompoundRecord<'b>,
+ xidmap: &mut NFSRequestXidMap,
+ ) {
+ let mut last_putfh: Option<&'b [u8]> = None;
+ let mut main_opcode: u32 = 0;
+ let mut aux_opcodes: Vec<u32> = Vec::new();
+
+ for c in &cr.commands {
+ SCLogDebug!("c {:?}", c);
+ match *c {
+ Nfs4RequestContent::PutFH(ref rd) => {
+ last_putfh = Some(rd.value);
+ aux_opcodes.push(NFSPROC4_PUTFH);
+ }
+ Nfs4RequestContent::Read(ref rd) => {
+ SCLogDebug!("READv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ xidmap.chunk_offset = rd.offset;
+ xidmap.file_handle = fh.to_vec();
+ self.xidmap_handle2name(xidmap);
+ }
+ }
+ Nfs4RequestContent::Open(ref rd) => {
+ SCLogDebug!("OPENv4: {}", String::from_utf8_lossy(rd.filename));
+ xidmap.file_name = rd.filename.to_vec();
+ }
+ Nfs4RequestContent::Lookup(ref rd) => {
+ SCLogDebug!("LOOKUPv4: {}", String::from_utf8_lossy(rd.filename));
+ xidmap.file_name = rd.filename.to_vec();
+ }
+ Nfs4RequestContent::Write(ref rd) => {
+ SCLogDebug!("WRITEv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ self.write_v4(r, rd, fh);
+ }
+ }
+ Nfs4RequestContent::Commit => {
+ SCLogDebug!("COMMITv4");
+ if let Some(fh) = last_putfh {
+ self.commit_v4(r, fh);
+ }
+ }
+ Nfs4RequestContent::Close(ref _rd) => {
+ SCLogDebug!("CLOSEv4: {:?}", _rd);
+ if let Some(fh) = last_putfh {
+ self.close_v4(r, fh);
+ }
+ }
+ Nfs4RequestContent::Create(ref rd) => {
+ SCLogDebug!("CREATEv4: {:?}", rd);
+ if let Some(fh) = last_putfh {
+ xidmap.file_handle = fh.to_vec();
+ }
+ xidmap.file_name = rd.filename.to_vec();
+ main_opcode = NFSPROC4_CREATE;
+ }
+ Nfs4RequestContent::Remove(rd) => {
+ SCLogDebug!("REMOVEv4: {:?}", rd);
+ xidmap.file_name = rd.to_vec();
+ main_opcode = NFSPROC4_REMOVE;
+ }
+ Nfs4RequestContent::SetClientId(ref _rd) => {
+ SCLogDebug!(
+ "SETCLIENTIDv4: client id {} r_netid {} r_addr {}",
+ String::from_utf8_lossy(_rd.client_id),
+ String::from_utf8_lossy(_rd.r_netid),
+ String::from_utf8_lossy(_rd.r_addr)
+ );
+ }
+ _ => {}
+ }
+ }
+
+ if main_opcode != 0 {
+ self.new_tx_v4(r, xidmap, main_opcode, &aux_opcodes);
+ }
+ }
+
+ /// complete request record
+ pub fn process_request_record_v4(&mut self, r: &RpcPacket) {
+ SCLogDebug!(
+ "NFSv4 REQUEST {} procedure {} ({}) blob size {}",
+ r.hdr.xid,
+ r.procedure,
+ self.requestmap.len(),
+ r.prog_data.len()
+ );
+
+ let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0);
+
+ if r.procedure == NFSPROC4_NULL {
+ if let RpcRequestCreds::GssApi(ref creds) = r.creds {
+ if creds.procedure == 1 {
+ let _x = parse_req_gssapi(r.prog_data);
+ SCLogDebug!("RPCSEC_GSS_INIT {:?}", _x);
+ }
+ }
+ } else if r.procedure == NFSPROC4_COMPOUND {
+ let mut data = r.prog_data;
+
+ if let RpcRequestCreds::GssApi(ref creds) = r.creds {
+ if creds.procedure == 0 && creds.service == 2 {
+ SCLogDebug!("GSS INTEGRITY: {:?}", creds);
+ match parse_rpc_gssapi_integrity(r.prog_data) {
+ Ok((_rem, rec)) => {
+ SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
+ data = rec.data;
+ // store proc and serv for the reply
+ xidmap.gssapi_proc = creds.procedure;
+ xidmap.gssapi_service = creds.service;
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!(
+ "NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}",
+ _e
+ );
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ }
+ }
+ }
+
+ match parse_nfs4_request_compound(data) {
+ Ok((_, rd)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd);
+ self.compound_request(r, &rd, &mut xidmap);
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND: Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ };
+ }
+
+ self.requestmap.insert(r.hdr.xid, xidmap);
+ }
+
+ fn compound_response<'b>(
+ &mut self, r: &RpcReplyPacket<'b>, cr: &Nfs4ResponseCompoundRecord<'b>,
+ xidmap: &mut NFSRequestXidMap,
+ ) {
+ let mut insert_filename_with_getfh = false;
+ let mut main_opcode_status: u32 = 0;
+ let mut main_opcode_status_set: bool = false;
+
+ for c in &cr.commands {
+ SCLogDebug!("c {:?}", c);
+ match *c {
+ Nfs4ResponseContent::ReadDir(_s, Some(ref rd)) => {
+ SCLogDebug!("READDIRv4: status {} eof {}", _s, rd.eof);
+
+ #[allow(clippy::manual_flatten)]
+ for d in &rd.listing {
+ if let Some(_d) = d {
+ SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(_d.name));
+ }
+ }
+ }
+ Nfs4ResponseContent::Remove(s) => {
+ SCLogDebug!("REMOVE4: status {}", s);
+ main_opcode_status = s;
+ main_opcode_status_set = true;
+ }
+ Nfs4ResponseContent::Create(s) => {
+ SCLogDebug!("CREATE4: status {}", s);
+ main_opcode_status = s;
+ main_opcode_status_set = true;
+ }
+ Nfs4ResponseContent::Read(s, Some(ref rd)) => {
+ SCLogDebug!(
+ "READ4: xidmap {:?} status {} data {}",
+ xidmap,
+ s,
+ rd.data.len()
+ );
+ // convert record to generic read reply
+ let reply = NfsReplyRead {
+ status: s,
+ attr_follows: 0,
+ attr_blob: &[],
+ count: rd.count,
+ eof: rd.eof,
+ data_len: rd.data.len() as u32,
+ data: rd.data,
+ };
+ self.process_read_record(r, &reply, Some(xidmap));
+ }
+ Nfs4ResponseContent::Open(_s, Some(ref _rd)) => {
+ SCLogDebug!("OPENv4: status {} opendata {:?}", _s, _rd);
+ insert_filename_with_getfh = true;
+ }
+ Nfs4ResponseContent::GetFH(_s, Some(ref rd)) => {
+ if insert_filename_with_getfh {
+ self.namemap
+ .insert(rd.value.to_vec(), xidmap.file_name.to_vec());
+ }
+ }
+ Nfs4ResponseContent::PutRootFH(s) => {
+ if s == NFS4_OK && xidmap.file_name.is_empty() {
+ xidmap.file_name = b"<mount_root>".to_vec();
+ SCLogDebug!("filename {:?}", xidmap.file_name);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ if main_opcode_status_set {
+ let resp_handle = Vec::new();
+ self.mark_response_tx_done(r.hdr.xid, r.reply_state, main_opcode_status, &resp_handle);
+ }
+ }
+
+ pub fn process_reply_record_v4(
+ &mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap,
+ ) {
+ if xidmap.procedure == NFSPROC4_COMPOUND {
+ let mut data = r.prog_data;
+
+ if xidmap.gssapi_proc == 0 && xidmap.gssapi_service == 2 {
+ SCLogDebug!("GSS INTEGRITY as set by call: {:?}", xidmap);
+ match parse_rpc_gssapi_integrity(r.prog_data) {
+ Ok((_rem, rec)) => {
+ SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
+ data = rec.data;
+ }
+ Err(Err::Incomplete(_n)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ return;
+ }
+ }
+ }
+ match parse_nfs4_response_compound(data) {
+ Ok((_, rd)) => {
+ SCLogDebug!("COMPOUNDv4: {:?}", rd);
+ self.compound_response(r, &rd, xidmap);
+ }
+ Err(Err::Incomplete(_)) => {
+ self.set_event(NFSEvent::MalformedData);
+ }
+ Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
+ SCLogDebug!("Parsing failed: {:?}", _e);
+ self.set_event(NFSEvent::MalformedData);
+ }
+ };
+ }
+ }
+}
diff --git a/rust/src/nfs/nfs4_records.rs b/rust/src/nfs/nfs4_records.rs
new file mode 100644
index 0000000..9d61da3
--- /dev/null
+++ b/rust/src/nfs/nfs4_records.rs
@@ -0,0 +1,2074 @@
+/* Copyright (C) 2018 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 NFSv4 records
+use nom7::bytes::streaming::{tag, take};
+use nom7::combinator::{complete, cond, map, peek, verify, rest};
+use nom7::error::{make_error, ErrorKind};
+use nom7::multi::{count, many_till};
+use nom7::number::streaming::{be_u32, be_u64};
+use nom7::{Err, IResult};
+
+use crate::nfs::types::*;
+
+/*https://datatracker.ietf.org/doc/html/rfc7530 - section 16.16 File Delegation Types */
+const OPEN_DELEGATE_NONE: u32 = 0;
+const OPEN_DELEGATE_READ: u32 = 1;
+const OPEN_DELEGATE_WRITE: u32 = 2;
+
+const RPCSEC_GSS: u32 = 6;
+
+// Maximum number of operations per compound
+// Linux defines NFSD_MAX_OPS_PER_COMPOUND to 16 (tested in Linux 5.15.1).
+const NFSD_MAX_OPS_PER_COMPOUND: usize = 64;
+
+#[derive(Debug,PartialEq, Eq)]
+pub enum Nfs4RequestContent<'a> {
+ PutFH(Nfs4Handle<'a>),
+ GetFH,
+ SaveFH,
+ PutRootFH,
+ ReadDir,
+ Commit,
+ Open(Nfs4RequestOpen<'a>),
+ Lookup(Nfs4RequestLookup<'a>),
+ Read(Nfs4RequestRead<'a>),
+ Write(Nfs4RequestWrite<'a>),
+ Close(Nfs4StateId<'a>),
+ Rename(Nfs4RequestRename<'a>),
+ Create(Nfs4RequestCreate<'a>),
+ OpenConfirm(Nfs4RequestOpenConfirm<'a>),
+ Access(u32),
+ GetAttr(Nfs4Attr),
+ SetAttr(Nfs4RequestSetAttr<'a>),
+ Renew(u64),
+ Remove(&'a[u8]),
+ DelegReturn(Nfs4StateId<'a>),
+ SetClientId(Nfs4RequestSetClientId<'a>),
+ SetClientIdConfirm,
+ ExchangeId(Nfs4RequestExchangeId<'a>),
+ Sequence(Nfs4RequestSequence<'a>),
+ CreateSession(Nfs4RequestCreateSession<'a>),
+ ReclaimComplete(u32),
+ SecInfoNoName(u32),
+ LayoutGet(Nfs4RequestLayoutGet<'a>),
+ GetDevInfo(Nfs4RequestGetDevInfo<'a>),
+ LayoutReturn(Nfs4RequestLayoutReturn<'a>),
+ DestroySession(&'a[u8]),
+ DestroyClientID(&'a[u8]),
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4Attr {
+ attr_mask: u64,
+}
+
+fn nfs4_parse_attr_fields(i: &[u8]) -> IResult<&[u8], u32> {
+ let (i, len) = be_u32(i)?;
+ let (i, _) = take(len as usize)(i)?;
+ Ok((i, len))
+}
+
+fn nfs4_parse_attrs(i: &[u8]) -> IResult<&[u8], Nfs4Attr> {
+ let (i, attr_cnt) = be_u32(i)?;
+ let (i, attr_mask1) = be_u32(i)?;
+ let (i, attr_mask2) = cond(attr_cnt >= 2, be_u32)(i)?;
+ let (i, _) = cond(attr_cnt == 3, be_u32)(i)?;
+ let (i, _) = nfs4_parse_attr_fields(i)?;
+ let attr = Nfs4Attr {
+ attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64,
+ };
+ Ok((i, attr))
+}
+
+fn nfs4_parse_attrbits(i: &[u8]) -> IResult<&[u8], Nfs4Attr> {
+ let (i, attr_cnt) = be_u32(i)?;
+ let (i, attr_mask1) = be_u32(i)?;
+ let (i, attr_mask2) = cond(attr_cnt >= 2, be_u32)(i)?;
+ let (i, _) = cond(attr_cnt == 3, be_u32)(i)?;
+ let attr = Nfs4Attr {
+ attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64,
+ };
+ Ok((i, attr))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4StateId<'a> {
+ pub seqid: u32,
+ pub data: &'a[u8],
+}
+
+fn nfs4_parse_stateid(i: &[u8]) -> IResult<&[u8], Nfs4StateId> {
+ let (i, seqid) = be_u32(i)?;
+ let (i, data) = take(12_usize)(i)?;
+ let state = Nfs4StateId { seqid, data };
+ Ok((i, state))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4Handle<'a> {
+ pub len: u32,
+ pub value: &'a[u8],
+}
+
+fn nfs4_parse_handle(i: &[u8]) -> IResult<&[u8], Nfs4Handle> {
+ let (i, len) = be_u32(i)?;
+ let (i, value) = take(len as usize)(i)?;
+ let handle = Nfs4Handle { len, value };
+ Ok((i, handle))
+}
+
+fn nfs4_parse_nfsstring(i: &[u8]) -> IResult<&[u8], &[u8]> {
+ let (i, len) = be_u32(i)?;
+ let (i, data) = take(len as usize)(i)?;
+ let (i, _fill_bytes) = cond(len % 4 != 0, take(4 - (len % 4)))(i)?;
+ Ok((i, data))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4RequestLayoutReturn<'a> {
+ pub layout_type: u32,
+ pub return_type: u32,
+ pub length: u64,
+ pub stateid: Nfs4StateId<'a>,
+ pub lrf_data: &'a[u8],
+}
+
+fn nfs4_req_layoutreturn(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _reclaim) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, layout_type) = be_u32(i)?;
+ let (i, _iq_mode) = be_u32(i)?;
+ let (i, return_type) = be_u32(i)?;
+ let (i, _offset) = be_u64(i)?;
+ let (i, length) = be_u64(i)?;
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, lrf_data) = nfs4_parse_nfsstring(i)?;
+ let req = Nfs4RequestContent::LayoutReturn(Nfs4RequestLayoutReturn {
+ layout_type,
+ return_type,
+ length,
+ stateid,
+ lrf_data,
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4RequestGetDevInfo<'a> {
+ pub device_id: &'a[u8],
+ pub layout_type: u32,
+ pub maxcount: u32,
+ pub notify_mask: u32,
+}
+
+fn nfs4_req_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, device_id) = take(16_usize)(i)?;
+ let (i, layout_type) = be_u32(i)?;
+ let (i, maxcount) = be_u32(i)?;
+ let (i, _) = be_u32(i)?;
+ let (i, notify_mask) = be_u32(i)?;
+ let req = Nfs4RequestContent::GetDevInfo(Nfs4RequestGetDevInfo {
+ device_id,
+ layout_type,
+ maxcount,
+ notify_mask,
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4RequestCreateSession<'a> {
+ pub client_id: &'a[u8],
+ pub seqid: u32,
+ pub machine_name: &'a[u8],
+}
+
+fn nfs4_req_create_session(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, client_id) = take(8_usize)(i)?;
+ let (i, seqid) = be_u32(i)?;
+ let (i, _flags) = be_u32(i)?;
+ let (i, _fore_chan_attrs) = take(28_usize)(i)?;
+ let (i, _back_chan_attrs) = take(28_usize)(i)?;
+ let (i, _cb_program) = be_u32(i)?;
+ let (i, _) = be_u32(i)?;
+ let (i, _flavor) = be_u32(i)?;
+ let (i, _stamp) = be_u32(i)?;
+ let (i, machine_name) = nfs4_parse_nfsstring(i)?;
+ let (i, _) = rest(i)?;
+
+ let req = Nfs4RequestContent::CreateSession(Nfs4RequestCreateSession {
+ client_id,
+ seqid,
+ machine_name,
+ });
+ Ok((i, req))
+}
+
+fn nfs4_req_putfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(nfs4_parse_handle, Nfs4RequestContent::PutFH)(i)
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestSetClientId<'a> {
+ pub client_id: &'a[u8],
+ pub r_netid: &'a[u8],
+ pub r_addr: &'a[u8],
+}
+
+fn nfs4_req_setclientid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _client_verifier) = take(8_usize)(i)?;
+ let (i, client_id) = nfs4_parse_nfsstring(i)?;
+ let (i, _cb_program) = be_u32(i)?;
+ let (i, r_netid) = nfs4_parse_nfsstring(i)?;
+ let (i, r_addr) = nfs4_parse_nfsstring(i)?;
+ let (i, _cb_id) = be_u32(i)?;
+ let req = Nfs4RequestContent::SetClientId(Nfs4RequestSetClientId {
+ client_id,
+ r_netid,
+ r_addr
+ });
+ Ok((i, req))
+}
+
+fn nfs4_req_setclientid_confirm(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _client_id) = take(8_usize)(i)?;
+ let (i, _verifier) = take(8_usize)(i)?;
+ Ok((i, Nfs4RequestContent::SetClientIdConfirm))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestCreate<'a> {
+ pub ftype4: u32,
+ pub filename: &'a[u8],
+ pub link_content: &'a[u8],
+}
+
+fn nfs4_req_create(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, ftype4) = be_u32(i)?;
+ let (i, link_content) = cond(ftype4 == 5, nfs4_parse_nfsstring)(i)?;
+ let (i, filename) = nfs4_parse_nfsstring(i)?;
+ let (i, _attrs) = nfs4_parse_attrs(i)?;
+ let req = Nfs4RequestContent::Create(Nfs4RequestCreate {
+ ftype4,
+ filename,
+ link_content: link_content.unwrap_or(&[]),
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub enum Nfs4OpenRequestContent<'a> {
+ Exclusive4(&'a[u8]),
+ Unchecked4(Nfs4Attr),
+ Guarded4(Nfs4Attr),
+}
+
+fn nfs4_req_open_unchecked4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> {
+ map(nfs4_parse_attrs, Nfs4OpenRequestContent::Unchecked4)(i)
+}
+
+fn nfs4_req_open_guarded4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> {
+ map(nfs4_parse_attrs, Nfs4OpenRequestContent::Guarded4)(i)
+}
+
+fn nfs4_req_open_exclusive4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> {
+ map(take(8_usize), Nfs4OpenRequestContent::Exclusive4)(i)
+}
+
+fn nfs4_req_open_type(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> {
+ let (i, mode) = be_u32(i)?;
+ let (i, data) = match mode {
+ 0 => nfs4_req_open_unchecked4(i)?,
+ 1 => nfs4_req_open_guarded4(i)?,
+ 2 => nfs4_req_open_exclusive4(i)?,
+ _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); }
+ };
+ Ok((i, data))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestOpen<'a> {
+ pub open_type: u32,
+ pub filename: &'a[u8],
+ pub open_data: Option<Nfs4OpenRequestContent<'a>>,
+}
+
+fn nfs4_req_open(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _seq_id) = be_u32(i)?;
+ let (i, _share_access) = be_u32(i)?;
+ let (i, _share_deny) = be_u32(i)?;
+ let (i, _client_id) = be_u64(i)?;
+ let (i, owner_len) = be_u32(i)?;
+ let (i, _) = cond(owner_len > 0, take(owner_len as usize))(i)?;
+ let (i, open_type) = be_u32(i)?;
+ let (i, open_data) = cond(open_type == 1, nfs4_req_open_type)(i)?;
+ let (i, _claim_type) = be_u32(i)?;
+ let (i, filename) = nfs4_parse_nfsstring(i)?;
+ let req = Nfs4RequestContent::Open(Nfs4RequestOpen {
+ open_type,
+ filename,
+ open_data
+ });
+ Ok((i, req))
+}
+
+fn nfs4_req_readdir(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _cookie) = be_u64(i)?;
+ let (i, _cookie_verf) = be_u64(i)?;
+ let (i, _dir_cnt) = be_u32(i)?;
+ let (i, _max_cnt) = be_u32(i)?;
+ let (i, _attr) = nfs4_parse_attrbits(i)?;
+ Ok((i, Nfs4RequestContent::ReadDir))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestRename<'a> {
+ pub oldname: &'a[u8],
+ pub newname: &'a[u8],
+}
+
+fn nfs4_req_rename(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, oldname) = nfs4_parse_nfsstring(i)?;
+ let (i, newname) = nfs4_parse_nfsstring(i)?;
+ let req = Nfs4RequestContent::Rename(Nfs4RequestRename {
+ oldname,
+ newname
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestLookup<'a> {
+ pub filename: &'a[u8],
+}
+
+fn nfs4_req_destroy_session(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, ssn_id) = take(16_usize)(i)?;
+ Ok((i, Nfs4RequestContent::DestroySession(ssn_id)))
+}
+
+fn nfs4_req_lookup(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(nfs4_parse_nfsstring, |filename| {
+ Nfs4RequestContent::Lookup(Nfs4RequestLookup { filename })
+ })(i)
+}
+
+fn nfs4_req_remove(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(nfs4_parse_nfsstring, Nfs4RequestContent::Remove)(i)
+}
+
+fn nfs4_req_secinfo_no_name(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(be_u32, Nfs4RequestContent::SecInfoNoName) (i)
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestSetAttr<'a> {
+ pub stateid: Nfs4StateId<'a>,
+}
+
+fn nfs4_req_setattr(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _attrs) = nfs4_parse_attrs(i)?;
+ let req = Nfs4RequestContent::SetAttr(Nfs4RequestSetAttr { stateid });
+ Ok((i, req))
+}
+
+fn nfs4_req_getattr(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(nfs4_parse_attrbits, Nfs4RequestContent::GetAttr)(i)
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestWrite<'a> {
+ pub stateid: Nfs4StateId<'a>,
+ pub offset: u64,
+ pub stable: u32,
+ pub write_len: u32,
+ pub data: &'a[u8],
+}
+
+fn nfs4_req_write(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, offset) = be_u64(i)?;
+ let (i, stable) = be_u32(i)?;
+ let (i, write_len) = be_u32(i)?;
+ let (i, data) = take(write_len as usize)(i)?;
+ let (i, _padding) = cond(write_len % 4 != 0, take(4 - (write_len % 4)))(i)?;
+ let req = Nfs4RequestContent::Write(Nfs4RequestWrite {
+ stateid,
+ offset,
+ stable,
+ write_len,
+ data,
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestRead<'a> {
+ pub stateid: Nfs4StateId<'a>,
+ pub offset: u64,
+ pub count: u32,
+}
+
+fn nfs4_req_read(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, offset) = be_u64(i)?;
+ let (i, count) = be_u32(i)?;
+ let req = Nfs4RequestContent::Read(Nfs4RequestRead {
+ stateid,
+ offset,
+ count,
+ });
+ Ok((i, req))
+}
+
+fn nfs4_req_close(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _seq_id) = be_u32(i)?;
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ Ok((i, Nfs4RequestContent::Close(stateid)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestOpenConfirm<'a> {
+ pub stateid: Nfs4StateId<'a>,
+}
+
+fn nfs4_req_open_confirm(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _seq_id) = be_u32(i)?;
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let req = Nfs4RequestContent::OpenConfirm(Nfs4RequestOpenConfirm {
+ stateid
+ });
+ Ok((i, req))
+}
+
+fn nfs4_req_delegreturn(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(nfs4_parse_stateid, Nfs4RequestContent::DelegReturn)(i)
+}
+
+fn nfs4_req_renew(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(be_u64, Nfs4RequestContent::Renew)(i)
+}
+
+fn nfs4_req_getfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ Ok((i, Nfs4RequestContent::GetFH))
+}
+
+fn nfs4_req_savefh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ Ok((i, Nfs4RequestContent::SaveFH))
+}
+
+fn nfs4_req_putrootfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ Ok((i, Nfs4RequestContent::PutRootFH))
+}
+
+fn nfs4_req_access(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(be_u32, Nfs4RequestContent::Access)(i)
+}
+
+fn nfs4_req_commit(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _offset) = be_u64(i)?;
+ let (i, _count) = be_u32(i)?;
+ Ok((i, Nfs4RequestContent::Commit))
+}
+
+fn nfs4_req_reclaim_complete(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ map(verify(be_u32, |&v| v <= 1), Nfs4RequestContent::ReclaimComplete) (i)
+}
+
+fn nfs4_req_destroy_clientid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, client_id) = take(8_usize)(i)?;
+ Ok((i, Nfs4RequestContent::DestroyClientID(client_id)))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4RequestLayoutGet<'a> {
+ pub layout_type: u32,
+ pub length: u64,
+ pub min_length: u64,
+ pub stateid: Nfs4StateId<'a>,
+}
+
+fn nfs4_req_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _layout_available) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, layout_type) = be_u32(i)?;
+ let (i, _iq_mode) = be_u32(i)?;
+ let (i, _offset) = be_u64(i)?;
+ let (i, length) = be_u64(i)?;
+ let (i, min_length) = be_u64(i)?;
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _maxcount) = be_u32(i)?;
+ let req = Nfs4RequestContent::LayoutGet(Nfs4RequestLayoutGet {
+ layout_type,
+ length,
+ min_length,
+ stateid,
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestExchangeId<'a> {
+ pub client_string: &'a[u8],
+ pub nii_domain: &'a[u8],
+ pub nii_name: &'a[u8],
+}
+
+fn nfs4_req_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, _verifier) = take(8_usize)(i)?;
+ let (i, eia_clientstring) = nfs4_parse_nfsstring(i)?;
+ let (i, _eia_clientflags) = be_u32(i)?;
+ let (i, _eia_state_protect) = be_u32(i)?;
+ let (i, _eia_client_impl_id) = be_u32(i)?;
+ let (i, nii_domain) = nfs4_parse_nfsstring(i)?;
+ let (i, nii_name) = nfs4_parse_nfsstring(i)?;
+ let (i, _nii_data_sec) = be_u64(i)?;
+ let (i, _nii_data_nsec) = be_u32(i)?;
+ let req = Nfs4RequestContent::ExchangeId(Nfs4RequestExchangeId {
+ client_string: eia_clientstring,
+ nii_domain,
+ nii_name
+ });
+ Ok((i, req))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestSequence<'a> {
+ pub ssn_id: &'a[u8],
+}
+
+fn nfs4_req_sequence(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, ssn_id) = take(16_usize)(i)?;
+ let (i, _seq_id) = be_u32(i)?;
+ let (i, _slot_id) = be_u32(i)?;
+ let (i, _high_slot_id) = be_u32(i)?;
+ let (i, _cache_this) = be_u32(i)?;
+ let req = Nfs4RequestContent::Sequence(Nfs4RequestSequence {
+ ssn_id
+ });
+ Ok((i, req))
+}
+
+fn parse_request_compound_command(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> {
+ let (i, cmd) = be_u32(i)?;
+ let (i, cmd_data) = match cmd {
+ NFSPROC4_PUTFH => nfs4_req_putfh(i)?,
+ NFSPROC4_READ => nfs4_req_read(i)?,
+ NFSPROC4_WRITE => nfs4_req_write(i)?,
+ NFSPROC4_GETFH => nfs4_req_getfh(i)?,
+ NFSPROC4_SAVEFH => nfs4_req_savefh(i)?,
+ NFSPROC4_OPEN => nfs4_req_open(i)?,
+ NFSPROC4_CLOSE => nfs4_req_close(i)?,
+ NFSPROC4_LOOKUP => nfs4_req_lookup(i)?,
+ NFSPROC4_ACCESS => nfs4_req_access(i)?,
+ NFSPROC4_COMMIT => nfs4_req_commit(i)?,
+ NFSPROC4_GETATTR => nfs4_req_getattr(i)?,
+ NFSPROC4_READDIR => nfs4_req_readdir(i)?,
+ NFSPROC4_RENEW => nfs4_req_renew(i)?,
+ NFSPROC4_OPEN_CONFIRM => nfs4_req_open_confirm(i)?,
+ NFSPROC4_REMOVE => nfs4_req_remove(i)?,
+ NFSPROC4_RENAME => nfs4_req_rename(i)?,
+ NFSPROC4_CREATE => nfs4_req_create(i)?,
+ NFSPROC4_DELEGRETURN => nfs4_req_delegreturn(i)?,
+ NFSPROC4_SETATTR => nfs4_req_setattr(i)?,
+ NFSPROC4_PUTROOTFH => nfs4_req_putrootfh(i)?,
+ NFSPROC4_SETCLIENTID => nfs4_req_setclientid(i)?,
+ NFSPROC4_SETCLIENTID_CONFIRM => nfs4_req_setclientid_confirm(i)?,
+ NFSPROC4_SEQUENCE => nfs4_req_sequence(i)?,
+ NFSPROC4_EXCHANGE_ID => nfs4_req_exchangeid(i)?,
+ NFSPROC4_CREATE_SESSION => nfs4_req_create_session(i)?,
+ NFSPROC4_RECLAIM_COMPLETE => nfs4_req_reclaim_complete(i)?,
+ NFSPROC4_SECINFO_NO_NAME => nfs4_req_secinfo_no_name(i)?,
+ NFSPROC4_LAYOUTGET => nfs4_req_layoutget(i)?,
+ NFSPROC4_GETDEVINFO => nfs4_req_getdevinfo(i)?,
+ NFSPROC4_LAYOUTRETURN => nfs4_req_layoutreturn(i)?,
+ NFSPROC4_DESTROY_SESSION => nfs4_req_destroy_session(i)?,
+ NFSPROC4_DESTROY_CLIENTID => nfs4_req_destroy_clientid(i)?,
+ _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); }
+ };
+ Ok((i, cmd_data))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4RequestCompoundRecord<'a> {
+ pub commands: Vec<Nfs4RequestContent<'a>>,
+}
+
+pub fn parse_nfs4_request_compound(i: &[u8]) -> IResult<&[u8], Nfs4RequestCompoundRecord> {
+ let (i, tag_len) = be_u32(i)?;
+ let (i, _tag) = cond(tag_len > 0, take(tag_len as usize))(i)?;
+ let (i, _min_ver) = be_u32(i)?;
+ let (i, ops_cnt) = be_u32(i)?;
+ if ops_cnt as usize > NFSD_MAX_OPS_PER_COMPOUND {
+ return Err(Err::Error(make_error(i, ErrorKind::Count)));
+ }
+ let (i, commands) = count(parse_request_compound_command, ops_cnt as usize)(i)?;
+ Ok((i, Nfs4RequestCompoundRecord { commands }))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub enum Nfs4ResponseContent<'a> {
+ PutFH(u32),
+ PutRootFH(u32),
+ GetFH(u32, Option<Nfs4Handle<'a>>),
+ Lookup(u32),
+ SaveFH(u32),
+ Rename(u32),
+ Write(u32, Option<Nfs4ResponseWrite>),
+ Read(u32, Option<Nfs4ResponseRead<'a>>),
+ Renew(u32),
+ Open(u32, Option<Nfs4ResponseOpen<'a>>),
+ OpenConfirm(u32, Option<Nfs4StateId<'a>>),
+ Close(u32, Option<Nfs4StateId<'a>>),
+ GetAttr(u32, Option<Nfs4Attr>),
+ SetAttr(u32),
+ Access(u32, Option<Nfs4ResponseAccess>),
+ ReadDir(u32, Option<Nfs4ResponseReaddir<'a>>),
+ Remove(u32),
+ DelegReturn(u32),
+ SetClientId(u32),
+ SetClientIdConfirm(u32),
+ Create(u32),
+ Commit(u32),
+ ExchangeId(u32, Option<Nfs4ResponseExchangeId<'a>>),
+ Sequence(u32, Option<Nfs4ResponseSequence<'a>>),
+ CreateSession(u32, Option<Nfs4ResponseCreateSession<'a>>),
+ ReclaimComplete(u32),
+ SecInfoNoName(u32),
+ LayoutGet(u32, Option<Nfs4ResponseLayoutGet<'a>>),
+ GetDevInfo(u32, Option<Nfs4ResponseGetDevInfo<'a>>),
+ LayoutReturn(u32),
+ DestroySession(u32),
+ DestroyClientID(u32),
+}
+
+// might need improvement with a stateid_present = yes case
+fn nfs4_res_layoutreturn(i:&[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _stateid_present) = verify(be_u32, |&v| v <= 1)(i)?;
+ Ok((i, Nfs4ResponseContent::LayoutReturn(status)))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4ResponseCreateSession<'a> {
+ pub ssn_id: &'a[u8],
+ pub seq_id: u32,
+}
+
+fn nfs4_parse_res_create_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseCreateSession> {
+ let (i, ssn_id) = take(16_usize)(i)?;
+ let (i, seq_id) = be_u32(i)?;
+ let (i, _flags) = be_u32(i)?;
+ let (i, _fore_chan_attrs) = take(28_usize)(i)?;
+ let (i, _back_chan_attrs) = take(28_usize)(i)?;
+ Ok((i, Nfs4ResponseCreateSession {
+ ssn_id,
+ seq_id
+ }))
+}
+
+fn nfs4_res_create_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, create_ssn_data) = cond(status == 0, nfs4_parse_res_create_session)(i)?;
+ Ok((i, Nfs4ResponseContent::CreateSession( status, create_ssn_data )))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4ResponseExchangeId<'a> {
+ pub client_id: &'a[u8],
+ pub eir_minorid: u64,
+ pub eir_majorid: &'a[u8],
+ pub nii_domain: &'a[u8],
+ pub nii_name: &'a[u8],
+}
+
+fn nfs4_parse_res_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseExchangeId> {
+ let (i, client_id) = take(8_usize)(i)?;
+ let (i, _seqid) = be_u32(i)?;
+ let (i, _flags) = be_u32(i)?;
+ let (i, _eia_state_protect) = be_u32(i)?;
+ let (i, eir_minorid) = be_u64(i)?;
+ let (i, eir_majorid) = nfs4_parse_nfsstring(i)?;
+ let (i, _server_scope) = nfs4_parse_nfsstring(i)?;
+ let (i, _eir_impl_id) = be_u32(i)?;
+ let (i, nii_domain) = nfs4_parse_nfsstring(i)?;
+ let (i, nii_name) = nfs4_parse_nfsstring(i)?;
+ let (i, _nii_date_sec) = be_u64(i)?;
+ let (i, _nii_date_nsec) = be_u32(i)?;
+ Ok((i, Nfs4ResponseExchangeId {
+ client_id,
+ eir_minorid,
+ eir_majorid,
+ nii_domain,
+ nii_name,
+ }))
+}
+
+fn nfs4_res_reclaim_complete(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::ReclaimComplete) (i)
+}
+
+fn nfs4_res_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, xchngid_data) = cond(status == 0, nfs4_parse_res_exchangeid)(i)?;
+ Ok((i, Nfs4ResponseContent::ExchangeId( status, xchngid_data)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseWrite {
+ pub count: u32,
+ pub committed: u32,
+}
+
+fn nfs4_res_write_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseWrite> {
+ let (i, count) = be_u32(i)?;
+ let (i, committed) = be_u32(i)?;
+ let (i, _verifier) = be_u64(i)?;
+ Ok((i, Nfs4ResponseWrite { count, committed }))
+}
+
+fn nfs4_res_write(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, wd) = cond(status == 0, nfs4_res_write_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Write(status, wd)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseRead<'a> {
+ pub eof: bool,
+ pub count: u32,
+ pub data: &'a[u8],
+}
+
+fn nfs4_res_read_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseRead> {
+ let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, read_len) = be_u32(i)?;
+ let (i, read_data) = take(read_len as usize)(i)?;
+ let resp = Nfs4ResponseRead {
+ eof: eof==1,
+ count: read_len,
+ data: read_data,
+ };
+ Ok((i, resp))
+}
+
+fn nfs4_res_read(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, rd) = cond(status == 0, nfs4_res_read_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Read(status, rd)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseOpen<'a> {
+ pub stateid: Nfs4StateId<'a>,
+ pub result_flags: u32,
+ pub delegate: Nfs4ResponseFileDelegation<'a>,
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum Nfs4ResponseFileDelegation<'a> {
+ DelegateRead(Nfs4ResponseOpenDelegateRead<'a>),
+ DelegateWrite(Nfs4ResponseOpenDelegateWrite<'a>),
+ DelegateNone(u32),
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4ResponseOpenDelegateWrite<'a> {
+ pub stateid: Nfs4StateId<'a>,
+ pub who: &'a[u8],
+}
+
+fn nfs4_res_open_ok_delegate_write(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _recall) = be_u32(i)?;
+ let (i, _space_limit) = be_u32(i)?;
+ let (i, _filesize) = be_u32(i)?;
+ let (i, _access_type) = be_u32(i)?;
+ let (i, _ace_flags) = be_u32(i)?;
+ let (i, _ace_mask) = be_u32(i)?;
+ let (i, who) = nfs4_parse_nfsstring(i)?;
+ Ok((i, Nfs4ResponseFileDelegation::DelegateWrite(Nfs4ResponseOpenDelegateWrite {
+ stateid,
+ who,
+ })))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseOpenDelegateRead<'a> {
+ pub stateid: Nfs4StateId<'a>,
+}
+
+fn nfs4_res_open_ok_delegate_read(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _recall) = be_u32(i)?;
+ let (i, _ace_type) = be_u32(i)?;
+ let (i, _ace_flags) = be_u32(i)?;
+ let (i, _ace_mask) = be_u32(i)?;
+ let (i, who_len) = be_u32(i)?;
+ let (i, _who) = take(who_len as usize)(i)?;
+ Ok((i, Nfs4ResponseFileDelegation::DelegateRead(Nfs4ResponseOpenDelegateRead {
+ stateid,
+ })))
+}
+
+fn nfs4_parse_file_delegation(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> {
+ let (i, delegation_type) = be_u32(i)?;
+ let (i, file_delegation) = match delegation_type {
+ OPEN_DELEGATE_READ => nfs4_res_open_ok_delegate_read(i)?,
+ OPEN_DELEGATE_WRITE => nfs4_res_open_ok_delegate_write(i)?,
+ OPEN_DELEGATE_NONE => (i, Nfs4ResponseFileDelegation::DelegateNone(OPEN_DELEGATE_NONE)),
+ _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); }
+ };
+ Ok((i, file_delegation))
+}
+
+fn nfs4_res_open_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseOpen> {
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _change_info) = take(20_usize)(i)?;
+ let (i, result_flags) = be_u32(i)?;
+ let (i, _attrs) = nfs4_parse_attrbits(i)?;
+ let (i, delegate) = nfs4_parse_file_delegation(i)?;
+ let resp = Nfs4ResponseOpen {
+ stateid,
+ result_flags,
+ delegate,
+ };
+ Ok((i, resp))
+}
+
+fn nfs4_res_open(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, open_data) = cond(status == 0, nfs4_res_open_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Open(status, open_data)))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4ResponseGetDevInfo<'a> {
+ pub layout_type: u32,
+ pub r_netid: &'a[u8],
+ pub r_addr: &'a[u8],
+ pub notify_mask: u32,
+}
+
+fn nfs4_parse_res_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4ResponseGetDevInfo> {
+ let (i, layout_type) = be_u32(i)?;
+ let (i, _) = be_u64(i)?;
+ let (i, _device_index) = be_u32(i)?;
+ let (i, _) = be_u64(i)?;
+ let (i, r_netid) = nfs4_parse_nfsstring(i)?;
+ let (i, r_addr) = nfs4_parse_nfsstring(i)?;
+ let (i, notify_mask) = be_u32(i)?;
+ Ok((i, Nfs4ResponseGetDevInfo {
+ layout_type,
+ r_netid,
+ r_addr,
+ notify_mask,
+ }))
+}
+
+fn nfs4_res_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, getdevinfo) = cond(status == 0, nfs4_parse_res_getdevinfo)(i)?;
+ Ok((i, Nfs4ResponseContent::GetDevInfo( status, getdevinfo )))
+}
+
+/*https://datatracker.ietf.org/doc/html/rfc5661#section-13.1*/
+// in case of multiple file handles, return handles in a vector
+#[derive(Debug, PartialEq, Eq)]
+pub struct Nfs4ResponseLayoutGet<'a> {
+ pub stateid: Nfs4StateId<'a>,
+ pub length: u64,
+ pub layout_type: u32,
+ pub device_id: &'a[u8],
+ pub file_handles: Vec<Nfs4Handle<'a>>,
+}
+
+fn nfs4_parse_res_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4ResponseLayoutGet> {
+ let (i, _return_on_close) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, stateid) = nfs4_parse_stateid(i)?;
+ let (i, _layout_seg) = be_u32(i)?;
+ let (i, _offset) = be_u64(i)?;
+ let (i, length) = be_u64(i)?;
+ let (i, _lo_mode) = be_u32(i)?;
+ let (i, layout_type) = be_u32(i)?;
+ let (i, _) = be_u32(i)?;
+ let (i, device_id) = take(16_usize)(i)?;
+ let (i, _nfl_util) = be_u32(i)?;
+ let (i, _strip_index) = be_u32(i)?;
+ let (i, _offset) = be_u64(i)?;
+ let (i, fh_handles) = be_u32(i)?;
+ // check before `count` allocates a vector
+ // so as not to run out of memory
+ if fh_handles as usize > 4 * i.len() {
+ return Err(Err::Error(make_error(i, ErrorKind::Count)));
+ }
+ let (i, file_handles) = count(nfs4_parse_handle, fh_handles as usize)(i)?;
+ Ok((i, Nfs4ResponseLayoutGet {
+ stateid,
+ length,
+ layout_type,
+ device_id,
+ file_handles,
+ }))
+}
+
+fn nfs4_res_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, lyg_data) = cond(status == 0, nfs4_parse_res_layoutget)(i)?;
+ Ok((i, Nfs4ResponseContent::LayoutGet( status, lyg_data )))
+}
+
+// #[derive(Debug, PartialEq)]
+// pub struct Nfs4FlavorRpcSecGss<'a> {
+// pub oid: &'a[u8],
+// pub qop: u32,
+// pub service: u32,
+// }
+
+fn nfs4_parse_rpcsec_gss(i: &[u8]) -> IResult<&[u8], u32> {
+ let (i, _oid) = nfs4_parse_nfsstring(i)?;
+ let (i, _qop) = be_u32(i)?;
+ let (i, _service) = be_u32(i)?;
+ Ok((i, RPCSEC_GSS))
+}
+
+fn nfs4_parse_flavors(i: &[u8]) -> IResult<&[u8], u32> {
+ let (i, flavor_type) = be_u32(i)?;
+ let (i, _flavor) = cond(flavor_type == RPCSEC_GSS, nfs4_parse_rpcsec_gss)(i)?;
+ Ok((i, flavor_type))
+}
+
+fn nfs4_res_secinfo_no_name(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, flavors_cnt) = be_u32(i)?;
+ // do not use nom's count as it allocates a Vector first
+ // which results in oom if flavors_cnt is really big, bigger than i.len()
+ let mut i2 = i;
+ for _n in 0..flavors_cnt {
+ let (i3, _flavor) = nfs4_parse_flavors(i2)?;
+ i2 = i3;
+ }
+ Ok((i, Nfs4ResponseContent::SecInfoNoName(status)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseReaddirEntry<'a> {
+ pub name: &'a[u8],
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseReaddir<'a> {
+ pub eof: bool,
+ pub listing: Vec<Option<Nfs4ResponseReaddirEntry<'a>>>,
+}
+
+fn nfs4_res_readdir_entry_do(i: &[u8]) -> IResult<&[u8], Nfs4ResponseReaddirEntry> {
+ let (i, _cookie) = be_u64(i)?;
+ let (i, name) = nfs4_parse_nfsstring(i)?;
+ let (i, _attrs) = nfs4_parse_attrs(i)?;
+ Ok((i, Nfs4ResponseReaddirEntry { name }))
+}
+
+fn nfs4_res_readdir_entry(i: &[u8]) -> IResult<&[u8], Option<Nfs4ResponseReaddirEntry>> {
+ let (i, value_follows) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, entry) = cond(value_follows == 1, nfs4_res_readdir_entry_do)(i)?;
+ Ok((i, entry))
+}
+
+fn nfs4_res_readdir_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseReaddir> {
+ let (i, _verifier) = be_u64(i)?;
+ // run parser until we find a 'value follows == 0'
+ let (i, listing) = many_till(
+ complete(nfs4_res_readdir_entry),
+ peek(tag(b"\x00\x00\x00\x00")),
+ )(i)?;
+ // value follows == 0 checked by line above
+ let (i, _value_follows) = tag(b"\x00\x00\x00\x00")(i)?;
+ let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?;
+ Ok((
+ i,
+ Nfs4ResponseReaddir {
+ eof: eof == 1,
+ listing: listing.0,
+ },
+ ))
+}
+
+fn nfs4_res_readdir(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, rd) = cond(status == 0, nfs4_res_readdir_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::ReadDir(status, rd)))
+}
+
+fn nfs4_res_create_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> {
+ let (i, _change_info) = take(20_usize)(i)?;
+ nfs4_parse_attrbits(i)
+}
+
+fn nfs4_res_create(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _attrs) = cond(status == 0, nfs4_res_create_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Create(status)))
+}
+
+fn nfs4_res_setattr_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> {
+ nfs4_parse_attrbits(i)
+}
+
+fn nfs4_res_setattr(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _attrs) = cond(status == 0, nfs4_res_setattr_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::SetAttr(status)))
+}
+
+fn nfs4_res_getattr_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> {
+ nfs4_parse_attrs(i)
+}
+
+fn nfs4_res_getattr(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, attrs) = cond(status == 0, nfs4_res_getattr_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::GetAttr(status, attrs)))
+}
+
+fn nfs4_res_openconfirm(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, stateid) = cond(status == 0, nfs4_parse_stateid)(i)?;
+ Ok((i, Nfs4ResponseContent::OpenConfirm(status, stateid)))
+}
+
+fn nfs4_res_close(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, stateid) = cond(status == 0, nfs4_parse_stateid)(i)?;
+ Ok((i, Nfs4ResponseContent::Close(status, stateid)))
+}
+
+fn nfs4_res_remove(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _) = cond(status == 0, take(20_usize))(i)?;
+ Ok((i, Nfs4ResponseContent::Remove(status)))
+}
+
+fn nfs4_res_rename(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::Rename)(i)
+}
+
+fn nfs4_res_savefh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::SaveFH)(i)
+}
+
+fn nfs4_res_lookup(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::Lookup)(i)
+}
+
+fn nfs4_res_renew(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::Renew)(i)
+}
+
+fn nfs4_res_getfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, fh) = cond(status == 0, nfs4_parse_handle)(i)?;
+ Ok((i, Nfs4ResponseContent::GetFH(status, fh)))
+}
+
+fn nfs4_res_putfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::PutFH)(i)
+}
+
+fn nfs4_res_putrootfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::PutRootFH)(i)
+}
+
+fn nfs4_res_delegreturn(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::DelegReturn)(i)
+}
+
+fn nfs4_res_setclientid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _client_id) = be_u64(i)?;
+ let (i, _verifier) = be_u32(i)?;
+ Ok((i, Nfs4ResponseContent::SetClientId(status)))
+}
+
+fn nfs4_res_setclientid_confirm(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::SetClientIdConfirm)(i)
+}
+
+fn nfs4_res_commit(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, _verifier) = cond(status == 0, take(8_usize))(i)?;
+ Ok((i, Nfs4ResponseContent::Commit(status)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseAccess {
+ pub supported_types: u32,
+ pub access_rights: u32,
+}
+
+fn nfs4_res_access_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseAccess> {
+ let (i, supported_types) = be_u32(i)?;
+ let (i, access_rights) = be_u32(i)?;
+ let resp = Nfs4ResponseAccess {
+ supported_types,
+ access_rights
+ };
+ Ok((i, resp))
+}
+
+fn nfs4_res_access(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, ad) = cond(status == 0, nfs4_res_access_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Access(status, ad)))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseSequence<'a> {
+ pub ssn_id: &'a[u8],
+}
+
+fn nfs4_res_sequence_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseSequence> {
+ let (i, ssn_id) = take(16_usize)(i)?;
+ let (i, _seqid) = be_u32(i)?;
+ let (i, _slots) = take(12_usize)(i)?;
+ let (i, _flags) = be_u32(i)?;
+ Ok((i, Nfs4ResponseSequence { ssn_id }))
+}
+
+fn nfs4_res_sequence(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, status) = be_u32(i)?;
+ let (i, seq) = cond(status == 0, nfs4_res_sequence_ok)(i)?;
+ Ok((i, Nfs4ResponseContent::Sequence(status, seq)))
+}
+
+fn nfs4_res_destroy_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::DestroySession) (i)
+}
+
+fn nfs4_res_destroy_clientid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ map(be_u32, Nfs4ResponseContent::DestroyClientID) (i)
+}
+
+fn nfs4_res_compound_command(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> {
+ let (i, cmd) = be_u32(i)?;
+ let (i, cmd_data) = match cmd {
+ NFSPROC4_READ => nfs4_res_read(i)?,
+ NFSPROC4_WRITE => nfs4_res_write(i)?,
+ NFSPROC4_ACCESS => nfs4_res_access(i)?,
+ NFSPROC4_COMMIT => nfs4_res_commit(i)?,
+ NFSPROC4_GETFH => nfs4_res_getfh(i)?,
+ NFSPROC4_PUTFH => nfs4_res_putfh(i)?,
+ NFSPROC4_SAVEFH => nfs4_res_savefh(i)?,
+ NFSPROC4_RENAME => nfs4_res_rename(i)?,
+ NFSPROC4_READDIR => nfs4_res_readdir(i)?,
+ NFSPROC4_GETATTR => nfs4_res_getattr(i)?,
+ NFSPROC4_SETATTR => nfs4_res_setattr(i)?,
+ NFSPROC4_LOOKUP => nfs4_res_lookup(i)?,
+ NFSPROC4_OPEN => nfs4_res_open(i)?,
+ NFSPROC4_OPEN_CONFIRM => nfs4_res_openconfirm(i)?,
+ NFSPROC4_CLOSE => nfs4_res_close(i)?,
+ NFSPROC4_REMOVE => nfs4_res_remove(i)?,
+ NFSPROC4_CREATE => nfs4_res_create(i)?,
+ NFSPROC4_DELEGRETURN => nfs4_res_delegreturn(i)?,
+ NFSPROC4_SETCLIENTID => nfs4_res_setclientid(i)?,
+ NFSPROC4_SETCLIENTID_CONFIRM => nfs4_res_setclientid_confirm(i)?,
+ NFSPROC4_PUTROOTFH => nfs4_res_putrootfh(i)?,
+ NFSPROC4_EXCHANGE_ID => nfs4_res_exchangeid(i)?,
+ NFSPROC4_SEQUENCE => nfs4_res_sequence(i)?,
+ NFSPROC4_RENEW => nfs4_res_renew(i)?,
+ NFSPROC4_CREATE_SESSION => nfs4_res_create_session(i)?,
+ NFSPROC4_RECLAIM_COMPLETE => nfs4_res_reclaim_complete(i)?,
+ NFSPROC4_SECINFO_NO_NAME => nfs4_res_secinfo_no_name(i)?,
+ NFSPROC4_LAYOUTGET => nfs4_res_layoutget(i)?,
+ NFSPROC4_GETDEVINFO => nfs4_res_getdevinfo(i)?,
+ NFSPROC4_LAYOUTRETURN => nfs4_res_layoutreturn(i)?,
+ NFSPROC4_DESTROY_SESSION => nfs4_res_destroy_session(i)?,
+ NFSPROC4_DESTROY_CLIENTID => nfs4_res_destroy_clientid(i)?,
+ _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); }
+ };
+ Ok((i, cmd_data))
+}
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct Nfs4ResponseCompoundRecord<'a> {
+ pub status: u32,
+ pub commands: Vec<Nfs4ResponseContent<'a>>,
+}
+
+pub fn parse_nfs4_response_compound(i: &[u8]) -> IResult<&[u8], Nfs4ResponseCompoundRecord> {
+ let (i, status) = be_u32(i)?;
+ let (i, tag_len) = be_u32(i)?;
+ let (i, _tag) = cond(tag_len > 0, take(tag_len as usize))(i)?;
+ let (i, ops_cnt) = be_u32(i)?;
+ if ops_cnt as usize > NFSD_MAX_OPS_PER_COMPOUND {
+ return Err(Err::Error(make_error(i, ErrorKind::Count)));
+ }
+ let (i, commands) = count(nfs4_res_compound_command, ops_cnt as usize)(i)?;
+ Ok((i, Nfs4ResponseCompoundRecord { status, commands }))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::nfs::nfs4_records::*;
+
+ #[test]
+ fn test_nfs4_request_compound() {
+ // Operations: SEQUENCE, PUTFH, CLOSE
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*Tag*/
+ 0x00, 0x00, 0x00, 0x01, /*min_ver*/
+ 0x00, 0x00, 0x00, 0x03, /*ops_cnt*/
+ // SEQUENCE
+ 0x00, 0x00, 0x00, 0x35, /*op_code*/
+ 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
+ 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // PUTFH
+ 0x00, 0x00, 0x00, 0x16, /*op_code*/
+ 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x84, 0x72, 0x00, 0x00, 0x23, 0xa6, 0xc0, 0x12,
+ 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ // CLOSE
+ 0x00, 0x00, 0x00, 0x04, /*op_code*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x88, 0xd9,
+ 0x04, 0x00, 0x00, 0x00,
+ ];
+
+ let sequence_buf: &[u8] = &buf[16..48];
+ let putfh_buf: &[u8] = &buf[52..88];
+ let close_buf: &[u8] = &buf[92..];
+
+ let (_, req_sequence) = nfs4_req_sequence(sequence_buf).unwrap();
+ let (_, req_putfh) = nfs4_req_putfh(putfh_buf).unwrap();
+ let (_, req_close) = nfs4_req_close(close_buf).unwrap();
+
+ let (_, compound_ops) = parse_nfs4_request_compound(buf).unwrap();
+ assert_eq!(compound_ops.commands[0], req_sequence);
+ assert_eq!(compound_ops.commands[1], req_putfh);
+ assert_eq!(compound_ops.commands[2], req_close);
+ }
+
+ #[test]
+ fn test_nfs4_request_setclientid() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x23, /*opcode*/
+ 0x59, 0x1b, 0x09, 0x04, 0x28, 0x9c, 0x5d, 0x10, /*_verifier*/
+ 0x00, 0x00, 0x00, 0x2d, 0x4c, 0x69, 0x6e, 0x75, /*client_id*/
+ 0x78, 0x20, 0x4e, 0x46, 0x53, 0x76, 0x34, 0x2e,
+ 0x30, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x39, 0x33,
+ 0x2e, 0x36, 0x37, 0x2e, 0x32, 0x32, 0x35, 0x2f,
+ 0x31, 0x30, 0x2e, 0x31, 0x39, 0x33, 0x2e, 0x36,
+ 0x37, 0x2e, 0x32, 0x31, 0x39, 0x20, 0x74, 0x63,
+ 0x70, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, /*_cb_program*/
+ 0x00, 0x00, 0x00, 0x03, 0x74, 0x63, 0x70, 0x00, /*r_netid*/
+ 0x00, 0x00, 0x00, 0x14, 0x31, 0x30, 0x2e, 0x31, /*r_addr*/
+ 0x39, 0x33, 0x2e, 0x36, 0x37, 0x2e, 0x32, 0x32,
+ 0x35, 0x2e, 0x31, 0x34, 0x30, 0x2e, 0x31, 0x38,
+ 0x00, 0x00, 0x00, 0x01, /*_cb_id*/
+ ];
+
+ let (_, req_client_id) = nfs4_parse_nfsstring(&buf[12..64]).unwrap();
+ let (_, req_r_netid) = nfs4_parse_nfsstring(&buf[68 ..76]).unwrap();
+ let (_, req_r_adrr) = nfs4_parse_nfsstring(&buf[76..100]).unwrap();
+
+ let (_, resquest) = nfs4_req_setclientid(&buf[4..]).unwrap();
+ match resquest {
+ Nfs4RequestContent::SetClientId( req_setclientid ) => {
+ assert_eq!(req_setclientid.client_id, req_client_id);
+ assert_eq!(req_setclientid.r_netid, req_r_netid);
+ assert_eq!(req_setclientid.r_addr, req_r_adrr);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_open() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x12, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*_seq_id*/
+ 0x00, 0x00, 0x00, 0x02, /*_share_access*/
+ 0x00, 0x00, 0x00, 0x00, /*_share_deny*/
+ 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2, /*_client_id*/
+ // OWNER
+ 0x00, 0x00, 0x00, 0x18, /*owner_len*/
+ 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x69, 0x64, 0x3a,
+ 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x48, 0x0c, 0xae, 0x9b, 0x05, 0x08,
+ // OPEN
+ 0x00, 0x00, 0x00, 0x01, /*open_type: OPEN4_CREATE*/
+ 0x00, 0x00, 0x00, 0x00, /*create_mode: UNCHECKED4*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, /*attr_mask*/
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0xb4,
+ // CLAIM_TYPE
+ 0x00, 0x00, 0x00, 0x00, /*_claim_type: CLAIM_NULL*/
+ 0x00, 0x00, 0x00, 0x04, 0x66, 0x69, 0x6c, 0x65, /*filename*/
+ ];
+
+ let (_, attr_buf) = nfs4_parse_attrbits(&buf[60..88]).unwrap();
+ let (_, filename_buf) = nfs4_parse_nfsstring(&buf[92..]).unwrap();
+
+ let (_, request) = nfs4_req_open(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::Open(req_open) => {
+ assert_eq!(req_open.open_type, 1);
+ assert_eq!(req_open.open_data, Some(Nfs4OpenRequestContent::Unchecked4(attr_buf)));
+ assert_eq!(req_open.filename, filename_buf);
+ }
+ _ => { panic!("Failure, {:?}", request); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_write() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x26, /*op_code*/
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x14, 0xe0, /*stateid*/
+ 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset*/
+ 0x00, 0x00, 0x00, 0x02, /*stable*/
+ 0x00, 0x00, 0x00, 0x05, /*write_len*/
+ 0x74, 0x65, 0x73, 0x74, 0x0a, /*data*/
+ 0x00, 0x00, 0x00, /*_padding*/
+ ];
+
+ let (_, stateid_buf) = nfs4_parse_stateid(&buf[4..20]).unwrap();
+
+ let (_, request) = nfs4_req_write(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::Write(req_write) => {
+ assert_eq!(req_write.stateid, stateid_buf);
+ assert_eq!(req_write.offset, 0);
+ assert_eq!(req_write.stable, 2);
+ assert_eq!(req_write.write_len, 5);
+ assert_eq!(req_write.data, "test\n".as_bytes());
+ }
+ _ => { panic!("Failure, {:?}", request); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_exchangeid() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2a, /*opcode*/
+ // eia_clientowner
+ 0x5c, 0x8a, 0x9b, 0xfe, 0x0c, 0x09, 0x5e, 0x92, /*_verifier*/
+ 0x00, 0x00, 0x00, 0x17, 0x4c, 0x69, 0x6e, 0x75, /*eia_clientstring*/
+ 0x78, 0x20, 0x4e, 0x46, 0x53, 0x76, 0x34, 0x2e,
+ 0x31, 0x20, 0x6e, 0x65, 0x74, 0x61, 0x70, 0x70,
+ 0x2d, 0x32, 0x36, 0x00,
+ 0x00, 0x00, 0x01, 0x01, /*_eia_clientflags*/
+ 0x00, 0x00, 0x00, 0x00, /*_eia_state_protect*/
+ // _eia_client_impl_id
+ 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x72, 0x6e, /*nii_domain*/
+ 0x65, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x45, 0x4c, 0x69, 0x6e, 0x75, /*nii_name*/
+ 0x78, 0x20, 0x33, 0x2e, 0x31, 0x30, 0x2e, 0x30,
+ 0x2d, 0x39, 0x35, 0x37, 0x2e, 0x65, 0x6c, 0x37,
+ 0x2e, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x20,
+ 0x23, 0x31, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x54,
+ 0x68, 0x75, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x34,
+ 0x20, 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x35,
+ 0x31, 0x20, 0x55, 0x54, 0x43, 0x20, 0x32, 0x30,
+ 0x31, 0x38, 0x20, 0x78, 0x38, 0x36, 0x5f, 0x36,
+ 0x34, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_nii_data_sec*/
+ 0x00, 0x00, 0x00, 0x00, /*_nii_data_nsec*/
+ ];
+
+ /*( .Linux NFSv4.1 netapp-26 )*/
+ let (_, client_string_buf) = nfs4_parse_nfsstring(&buf[12..40]).unwrap();
+ /*(kernel.org\0\0\0\n)*/
+ let (_, nii_domain_buf) = nfs4_parse_nfsstring(&buf[52..68]).unwrap();
+ /* ( ELinux 3.10.0-957.el7.x86_64 #1 SMP Thu Oct 4 20:48:51 UTC 2018 x86_64 ) */
+ let (_, nii_name_buf) = nfs4_parse_nfsstring(&buf[68..144]).unwrap();
+
+ let (_, request) = nfs4_req_exchangeid(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::ExchangeId(req_exchangeid) => {
+ assert_eq!(req_exchangeid.client_string, client_string_buf);
+ assert_eq!(req_exchangeid.nii_domain, nii_domain_buf);
+ assert_eq!(req_exchangeid.nii_name, nii_name_buf);
+ }
+ _ => { panic!("Failure, {:?}", request); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_close() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x04, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*_seq_id*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x82, 0x14, 0xe0, /*stateid*/
+ 0x5b, 0x00, 0x88, 0xd9, 0x04, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, stateid_buf) = nfs4_parse_stateid(&buf[8..]).unwrap();
+
+ let (_, request) = nfs4_req_close(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::Close(req_stateid) => {
+ assert_eq!(req_stateid, stateid_buf);
+ }
+ _ => { panic!("Failure, {:?}", request); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_sequence() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x35, /*opcode*/
+ 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, /*ssn_id*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
+ 0x00, 0x00, 0x00, 0x18, /*_seq_id*/
+ 0x00, 0x00, 0x00, 0x00, /*_slot_id*/
+ 0x00, 0x00, 0x00, 0x00, /*_high_slot_id*/
+ 0x00, 0x00, 0x00, 0x01, /*_catch_this*/
+ ];
+
+ let (_, req_sequence) = nfs4_req_sequence(&buf[4..]).unwrap();
+ match req_sequence {
+ Nfs4RequestContent::Sequence(seq_buf) => {
+ assert_eq!(seq_buf.ssn_id, &buf[4..20]);
+ }
+ _ => { panic!("Failure, {:?}", req_sequence); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_lookup() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x0f, /*opcode*/
+ 0x00, 0x00, 0x00, 0x04, 0x76, 0x6f, 0x6c, 0x31, /*filename: (vol1)*/
+ ];
+
+ let (_, filename_buf) = nfs4_parse_nfsstring(&buf[4..]).unwrap();
+
+ let (_, request) = nfs4_req_lookup(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::Lookup(req_lookup) => {
+ assert_eq!(req_lookup.filename, filename_buf);
+ }
+ _ => { panic!("Failure, {:?}", request); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_putfh() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x16, /*opcode*/
+ 0x00, 0x00, 0x00, 0x20, /*handle_len*/
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*handle*/
+ 0x00, 0x00, 0x00, 0x00, 0x84, 0x72, 0x00, 0x00,
+ 0x23, 0xa6, 0xc0, 0x12, 0x00, 0xf2, 0xfa, 0x80,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, handle_buf) = nfs4_parse_handle(&buf[4..]).unwrap();
+
+ let (_, result) = nfs4_req_putfh(&buf[4..]).unwrap();
+ match result {
+ Nfs4RequestContent::PutFH(putfh_handle) => {
+ assert_eq!(putfh_handle.value, handle_buf.value);
+ assert_eq!(putfh_handle.len, handle_buf.len);
+ }
+ _ => { panic!("Failure, {:?}", result); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_create_session() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2b, /*opcode*/
+ 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2, // create_session
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, 0x14,
+ 0x00, 0x10, 0x03, 0x88, 0x00, 0x00, 0x0d, 0x64,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x01, 0x0c, 0x09, 0x5e, 0x92,
+ 0x00, 0x00, 0x00, 0x09, 0x6e, 0x65, 0x74, 0x61,
+ 0x70, 0x70, 0x2d, 0x32, 0x36, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, request) = nfs4_req_create_session(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::CreateSession( create_ssn ) => {
+ assert_eq!(create_ssn.client_id, &buf[4..12]);
+ assert_eq!(create_ssn.seqid, 1);
+ assert_eq!(create_ssn.machine_name, b"netapp-26");
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_layoutget() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x32, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // layoutget
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x89, 0xd9,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ ];
+
+ let (_, stateid_buf) = nfs4_parse_stateid(&buf[40..56]).unwrap();
+ assert_eq!(stateid_buf.seqid, 0);
+
+ let (_, request) = nfs4_req_layoutget(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::LayoutGet( lyg_data ) => {
+ assert_eq!(lyg_data.layout_type, 1);
+ assert_eq!(lyg_data.min_length, 4096);
+ assert_eq!(lyg_data.stateid, stateid_buf);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_getdevinfo() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2f, /*opcode*/
+ 0x01, 0x01, 0x00, 0x00, 0x00, 0xf2, 0xfa, 0x80, // getdevinfo
+ 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3e, 0x20,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06,
+ ];
+
+ let (_, request) = nfs4_req_getdevinfo(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::GetDevInfo( getdevifo ) => {
+ assert_eq!(getdevifo.device_id, &buf[4..20]);
+ assert_eq!(getdevifo.layout_type, 1);
+ assert_eq!(getdevifo.maxcount, 81440);
+ assert_eq!(getdevifo.notify_mask, 6);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_layoutreturn() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x33, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // layoutreturn
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x01, 0x03, 0x82, 0x14, 0xe0,
+ 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, stateid_buf) = nfs4_parse_stateid(&buf[36..52]).unwrap();
+ assert_eq!(stateid_buf.seqid, 1);
+
+ let (_, request) = nfs4_req_layoutreturn(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::LayoutReturn( layoutreturn ) => {
+ assert_eq!(layoutreturn.layout_type, 1);
+ assert_eq!(layoutreturn.return_type, 1);
+ assert_eq!(layoutreturn.stateid, stateid_buf);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_request_destroy_session() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2c, /*opcode*/
+ 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, /*ssn_id*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
+ ];
+
+ let (_, request) = nfs4_req_destroy_session(&buf[4..]).unwrap();
+ match request {
+ Nfs4RequestContent::DestroySession( ssn_id ) => {
+ assert_eq!(ssn_id, &buf[4..]);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_attrs() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x09, /*opcode*/
+ 0x00, 0x00, 0x00, 0x03, /*attr_cnt*/
+ 0x00, 0x00, 0x20, 0x65, /*attr_mask[0]*/
+ 0x00, 0x00, 0x00, 0x00, /*attr_mask[1]*/
+ 0x00, 0x00, 0x08, 0x00, /*attr_mask[2]*/
+ ];
+
+ let (r, attr) = nfs4_parse_attrbits(&buf[4..]).unwrap();
+ assert_eq!(r.len(), 0);
+ // assert_eq!(attr.attr_mask, 35618163785728);
+ assert_eq!(attr.attr_mask, 0x00002065_u64 << 32);
+ }
+ #[test]
+ fn test_nfs4_response_compound() {
+ // Operations: SEQUENCE, PUTFH, CLOSE
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x00, /*Tag*/
+ 0x00, 0x00, 0x00, 0x03, /*ops_cnt*/
+ // SEQUENCE
+ 0x00, 0x00, 0x00, 0x35, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd2,
+ 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x18,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f,
+ 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00,
+ // PUTFH
+ 0x00, 0x00, 0x00, 0x16, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00,
+ // CLOSE
+ 0x00, 0x00, 0x00, 0x04, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+
+ ];
+
+ let sequence_buf: &[u8] = &buf[16..56];
+ let putfh_buf: &[u8] = &buf[60..64];
+ let close_buf: &[u8] = &buf[68..];
+
+ let (_, res_sequence) = nfs4_res_sequence(sequence_buf).unwrap();
+ let (_, res_putfh) = nfs4_res_putfh(putfh_buf).unwrap();
+ let (_, res_close) = nfs4_res_close(close_buf).unwrap();
+
+ let (_, compound_ops) = parse_nfs4_response_compound(buf).unwrap();
+ assert_eq!(compound_ops.status, 0);
+ assert_eq!(compound_ops.commands[0], res_sequence);
+ assert_eq!(compound_ops.commands[1], res_putfh);
+ assert_eq!(compound_ops.commands[2], res_close);
+ }
+
+ #[test]
+ fn test_nfs4_response_open() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x12, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ // open_data
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x82, 0x14, 0xe0, /*stateid*/
+ 0x5b, 0x00, 0x88, 0xd9, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x16, 0xf8, 0x2f, 0xd5, /*_change_info*/
+ 0xdb, 0xb7, 0xfe, 0x38, 0x16, 0xf8, 0x2f, 0xdf,
+ 0x21, 0xa8, 0x2a, 0x48, 0x00, 0x00, 0x00, 0x04,
+ 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, /*_attrs*/
+ 0x00, 0x00, 0x00, 0x00,
+ // delegate_write
+ 0x00, 0x00, 0x00, 0x02, /*delegation_type*/
+ 0x00, 0x00, 0x00, 0x01, 0x02, 0x82, 0x14, 0xe0,
+ 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let stateid_buf = &buf[8..24];
+ let (_, res_stateid) = nfs4_parse_stateid(stateid_buf).unwrap();
+
+ let delegate_buf = &buf[64..];
+ let (_, delegate) = nfs4_parse_file_delegation(delegate_buf).unwrap();
+
+ let open_data_buf = &buf[8..];
+ let (_, res_open_data) = nfs4_res_open_ok(open_data_buf).unwrap();
+ assert_eq!(res_open_data.stateid, res_stateid);
+ assert_eq!(res_open_data.result_flags, 4);
+ assert_eq!(res_open_data.delegate, delegate);
+
+ let (_, response) = nfs4_res_open(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::Open(status, open_data) => {
+ assert_eq!(status, 0);
+ assert_eq!(open_data, Some(res_open_data));
+ }
+ _ => { panic!("Failure, {:?}", response); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_write() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x26, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, /*wd*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ ];
+
+ let (_, wd_buf) = nfs4_res_write_ok(&buf[8..]).unwrap();
+ assert_eq!(wd_buf.count, 5);
+ assert_eq!(wd_buf.committed, 2);
+
+ let (_, result) = nfs4_res_write(&buf[4..]).unwrap();
+ match result {
+ Nfs4ResponseContent::Write(status, wd) => {
+ assert_eq!(status, 0);
+ assert_eq!(wd, Some(wd_buf));
+ }
+ _ => { panic!("Failure, {:?}", result); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_access() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x03, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, /*ad*/
+ ];
+
+ let (_, ad_buf) = nfs4_res_access_ok(&buf[8..]).unwrap();
+ assert_eq!(ad_buf.supported_types, 0x1f);
+ assert_eq!(ad_buf.access_rights, 0x1f);
+
+ let (_, result) = nfs4_res_access(&buf[4..]).unwrap();
+ match result {
+ Nfs4ResponseContent::Access(status, ad) => {
+ assert_eq!(status, 0);
+ assert_eq!(ad, Some(ad_buf));
+ }
+ _ => { panic!("Failure, {:?}", result); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_getfh() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x0a, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x00, 0x00, /*fh*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x40, 0x00, 0x00, 0x00, 0x8b, 0xae, 0xea, 0x7f,
+ 0xff, 0xf1, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, fh_buf) = nfs4_parse_handle(&buf[8..]).unwrap();
+
+ let (_, result) = nfs4_res_getfh(&buf[4..]).unwrap();
+ match result {
+ Nfs4ResponseContent::GetFH(status, fh) => {
+ assert_eq!(status, 0);
+ assert_eq!(fh, Some(fh_buf));
+ }
+ _ => { panic!("Failure, {:?}", result); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_getattr() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x09, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x03, /*attr_cnt*/
+ 0x00, 0x00, 0x20, 0x65, 0x00, 0x00, 0x00, 0x00, /*attr_mask*/
+ 0x00, 0x00, 0x08, 0x00,
+ 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03, /*attrs*/
+ 0xfa, 0xfe, 0xbf, 0xff, 0x60, 0xfd, 0xff, 0xfe,
+ 0x00, 0x00, 0x08, 0x17, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03,
+ 0x02, 0x00, 0x10, 0x00, 0x00, 0x24, 0x40, 0x32,
+ 0x00, 0x00, 0x00, 0x00
+ ];
+
+ let (_, attrs_buf) = nfs4_parse_attrs(&buf[8..]).unwrap();
+
+ let (_, attr_fields) = nfs4_parse_attr_fields(&buf[24..]).unwrap();
+ assert_eq!(attr_fields, 48);
+
+ let (_, result) = nfs4_res_getattr(&buf[4..]).unwrap();
+ match result {
+ Nfs4ResponseContent::GetAttr(status, attrs) => {
+ assert_eq!(status, 0);
+ assert_eq!(attrs, Some(attrs_buf));
+ }
+ _ => { panic!("Failure, {:?}", result); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_readdir() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x1a, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*Status: 0*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/
+ // directory_listing
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x55, 0xeb, 0x42, 0x33, /*entry0*/
+ 0x00, 0x00, 0x00, 0x06, 0x43, 0x65, 0x6e, 0x74, 0x4f, 0x53, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x18, 0x09, 0x1a, 0x00, 0xb0, 0xa2, 0x3a,
+ 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x02, 0xaf, 0x8f, 0x9b, 0x4e,
+ 0x29, 0xc4, 0xa2, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,
+ 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x52, 0x00,
+ 0xb0, 0x33, 0xf7, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x0b,
+ 0x00, 0x00, 0x01, 0xfd, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x12,
+ 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65,
+ 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12,
+ 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65,
+ 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x4e, 0x1f, 0x17, 0xbc, 0x28, 0x86, 0x38, 0x31,
+ 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9b, 0x8f, 0xaf, 0x1d, 0xa2, 0xc4, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9b, 0x8f, 0xaf, 0x1d, 0xa2, 0xc4, 0x29,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x0b,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, /*entry1*/
+ 0x00, 0x00, 0x00, 0x04, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x18, 0x09, 0x1a, 0x00, 0xb0, 0xa2, 0x3a, 0x00, 0x00, 0x00, 0xb0,
+ 0x00, 0x00, 0x00, 0x02, 0x83, 0x66, 0x9c, 0x4e, 0x25, 0x80, 0x82, 0x07,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x01, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x52, 0x00, 0xad, 0x37, 0xad, 0x2c,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x02, 0x00, 0x00, 0x03, 0xff,
+ 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x72, 0x6f, 0x6f, 0x74,
+ 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65, 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61,
+ 0x00, 0x00, 0x00, 0x10, 0x72, 0x6f, 0x6f, 0x74, 0x40, 0x66, 0x69, 0x61,
+ 0x6e, 0x65, 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x4d, 0x6a, 0x97, 0xdb, 0x33, 0x89, 0xba, 0x2d,
+ 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9c, 0x66, 0x83, 0x07, 0x82, 0x80, 0x25,
+ 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9c, 0x66, 0x83, 0x07, 0x82, 0x80, 0x25,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, /*value_follows*/
+ 0x00, 0x00, 0x00, 0x01, /*EOF: YES*/
+ ];
+
+ let entry0_buf = &buf[16..240];
+ let entry1_buf = &buf[240..452];
+
+ let (_, res_entry0) = nfs4_res_readdir_entry_do(&entry0_buf[4..]).unwrap();
+ assert_eq!(res_entry0.name, "CentOS".as_bytes());
+
+ let (_, res_entry1) = nfs4_res_readdir_entry_do(&entry1_buf[4..]).unwrap();
+ assert_eq!(res_entry1.name, "data".as_bytes());
+
+ let (_, res_rd) = nfs4_res_readdir_ok(&buf[8..]).unwrap();
+ assert!(res_rd.eof);
+ assert_eq!(res_rd.listing, [Some(res_entry0), Some(res_entry1)]);
+
+ let (_, response) = nfs4_res_readdir(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::ReadDir(status, rd) => {
+ assert_eq!(status, 0);
+ assert_eq!(rd, Some(res_rd));
+ }
+ _ => { panic!("Failure!"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_setclientid() {
+ #[rustfmt::skip]
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x23, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x14, 0x67, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, /*_clientid*/
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/
+ ];
+
+ let (_, response) = nfs4_res_setclientid(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::SetClientId(status) => {
+ assert_eq!(status, 0);
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_exchangeid() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2a, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ // exchange_id
+ 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x01, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x98, 0x3b, 0xa3, 0x1e,
+ 0xd7, 0xa9, 0x11, 0xe8, 0x00, 0x00, 0x00, 0x10,
+ 0x98, 0x3b, 0xa3, 0x1e, 0xd7, 0xa9, 0x11, 0xe8,
+ 0xbc, 0x0c, 0x00, 0x0c, 0x29, 0xe9, 0x13, 0x93,
+ 0x00, 0x00, 0x00, 0x10, 0x84, 0x8b, 0x93, 0x12,
+ 0xd7, 0xa9, 0x11, 0xe8, 0xbc, 0x0c, 0x00, 0x0c,
+ 0x29, 0xe9, 0x13, 0x93, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x65, 0x74, 0x61,
+ 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x24, 0x4e, 0x65, 0x74, 0x41,
+ 0x70, 0x70, 0x20, 0x52, 0x65, 0x6c, 0x65, 0x61,
+ 0x73, 0x65, 0x20, 0x56, 0x6f, 0x6f, 0x64, 0x6f,
+ 0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x5f,
+ 0x5f, 0x39, 0x2e, 0x36, 0x2e, 0x30, 0x00, 0x00,
+ 0x26, 0x0d, 0xcf, 0x5b, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, xchangeid) = nfs4_parse_res_exchangeid(&buf[8..]).unwrap();
+
+ let (_, response) = nfs4_res_exchangeid(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::ExchangeId(status, xchngid_data) => {
+ assert_eq!(status, 0);
+ assert_eq!(xchngid_data, Some(xchangeid));
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_create_session() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2b, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ // create_session
+ 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00,
+ 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x02, 0x80,
+ 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, create_ssn) = nfs4_parse_res_create_session(&buf[8..]).unwrap();
+
+ let (_, response) = nfs4_res_create_session(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::CreateSession( status, create_ssn_data) => {
+ assert_eq!(status, 0);
+ assert_eq!(create_ssn_data, Some(create_ssn));
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_layoutget() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x32, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ // layoutget
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x03, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x89, 0xd9,
+ 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x58, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x30, 0x01, 0x03, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x84, 0x72, 0x00, 0x00, 0x23, 0xa6, 0xc0, 0x12,
+ 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00,
+ 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x20, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, stateid) = nfs4_parse_stateid(&buf[12..28]).unwrap();
+
+ let (_, lyg_data) = nfs4_parse_res_layoutget(&buf[8..]).unwrap();
+ assert_eq!(lyg_data.stateid, stateid);
+ assert_eq!(lyg_data.layout_type, 1);
+ assert_eq!(lyg_data.device_id, &buf[60..76]);
+
+ let (_, response) = nfs4_res_layoutget(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::LayoutGet( status, lyg ) => {
+ assert_eq!(status, 0);
+ assert_eq!(lyg, Some(lyg_data));
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+
+ #[test]
+ fn test_nfs4_response_getdevinfo() {
+ let buf: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x2f, /*opcode*/
+ 0x00, 0x00, 0x00, 0x00, /*status*/
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, // getdevinfo
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x03, 0x74, 0x63, 0x70, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x31, 0x39, 0x32, 0x2e,
+ 0x31, 0x36, 0x38, 0x2e, 0x30, 0x2e, 0x36, 0x31,
+ 0x2e, 0x38, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ let (_, getdevinfo) = nfs4_parse_res_getdevinfo(&buf[8..]).unwrap();
+ assert_eq!(getdevinfo.layout_type, 1);
+ assert_eq!(getdevinfo.r_netid, b"tcp");
+ assert_eq!(getdevinfo.r_addr, b"192.168.0.61.8.1");
+
+ let (_, response) = nfs4_res_getdevinfo(&buf[4..]).unwrap();
+ match response {
+ Nfs4ResponseContent::GetDevInfo(status, getdevinfo_data) => {
+ assert_eq!(status, 0);
+ assert_eq!(getdevinfo_data, Some(getdevinfo))
+ }
+ _ => { panic!("Failure"); }
+ }
+ }
+}
diff --git a/rust/src/nfs/nfs_records.rs b/rust/src/nfs/nfs_records.rs
new file mode 100644
index 0000000..3b0804b
--- /dev/null
+++ b/rust/src/nfs/nfs_records.rs
@@ -0,0 +1,29 @@
+/* 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 NFS
+
+#[derive(Debug,PartialEq, Eq)]
+pub struct NfsReplyRead<'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
+}
diff --git a/rust/src/nfs/rpc_records.rs b/rust/src/nfs/rpc_records.rs
new file mode 100644
index 0000000..d373ae9
--- /dev/null
+++ b/rust/src/nfs/rpc_records.rs
@@ -0,0 +1,465 @@
+/* Copyright (C) 2017-2022 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 RPCv2
+
+use crate::common::nom7::bits;
+use nom7::bits::streaming::take as take_bits;
+use nom7::bytes::streaming::take;
+use nom7::combinator::{cond, verify, rest};
+use nom7::multi::length_data;
+use nom7::number::streaming::{be_u32};
+use nom7::sequence::tuple;
+use nom7::error::{make_error, ErrorKind};
+use nom7::{IResult, Err};
+
+pub const RPC_MAX_MACHINE_SIZE: u32 = 256; // Linux kernel defines 64.
+pub const RPC_MAX_CREDS_SIZE: u32 = 4096; // Linux kernel defines 400.
+pub const RPC_MAX_VERIFIER_SIZE: u32 = 4096; // Linux kernel defines 400.
+
+#[derive(Debug, PartialEq, Eq)]
+pub enum RpcRequestCreds<'a> {
+ Unix(RpcRequestCredsUnix<'a>),
+ GssApi(RpcRequestCredsGssApi<'a>),
+ Unknown(&'a [u8]),
+}
+
+#[derive(Debug, PartialEq, Eq)]
+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)
+//);
+
+fn parse_rpc_request_creds_unix(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> {
+ let (i, stamp) = be_u32(i)?;
+ let (i, machine_name_len) = verify(be_u32, |&size| size < RPC_MAX_MACHINE_SIZE)(i)?;
+ let (i, machine_name_buf) = take(machine_name_len as usize)(i)?;
+ let (i, uid) = be_u32(i)?;
+ let (i, gid) = be_u32(i)?;
+ // let (i, aux_gids) = parse_rpc_creds_unix_aux_gids(i)?;
+ let creds = RpcRequestCreds::Unix(RpcRequestCredsUnix {
+ stamp,
+ machine_name_len,
+ machine_name_buf,
+ uid,
+ gid,
+ aux_gids: None,
+ });
+ Ok((i, creds))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct RpcRequestCredsGssApi<'a> {
+ pub version: u32,
+ pub procedure: u32,
+ pub seq_num: u32,
+ pub service: u32,
+
+ pub ctx: &'a [u8],
+}
+
+fn parse_rpc_request_creds_gssapi(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> {
+ let (i, version) = be_u32(i)?;
+ let (i, procedure) = be_u32(i)?;
+ let (i, seq_num) = be_u32(i)?;
+ let (i, service) = be_u32(i)?;
+ let (i, ctx) = length_data(be_u32)(i)?;
+ let creds = RpcRequestCreds::GssApi(RpcRequestCredsGssApi {
+ version,
+ procedure,
+ seq_num,
+ service,
+ ctx,
+ });
+ Ok((i, creds))
+}
+
+fn parse_rpc_request_creds_unknown(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> {
+ Ok((&[], RpcRequestCreds::Unknown(i)))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct RpcGssApiIntegrity<'a> {
+ pub seq_num: u32,
+ pub data: &'a [u8],
+}
+
+// Parse the GSSAPI Integrity envelope to get to the
+// data we care about.
+pub fn parse_rpc_gssapi_integrity(i: &[u8]) -> IResult<&[u8], RpcGssApiIntegrity> {
+ let (i, len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?;
+ let (i, seq_num) = be_u32(i)?;
+ let (i, data) = take(len as usize)(i)?;
+ let res = RpcGssApiIntegrity { seq_num, data };
+ Ok((i, res))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct RpcPacketHeader {
+ pub frag_is_last: bool,
+ pub frag_len: u32,
+ pub xid: u32,
+ pub msgtype: u32,
+}
+
+fn parse_bits(i: &[u8]) -> IResult<&[u8], (u8, u32)> {
+ bits(tuple((
+ take_bits(1u8), // is_last
+ take_bits(31u32), // len
+ )))(i)
+}
+
+pub fn parse_rpc_packet_header(i: &[u8]) -> IResult<&[u8], RpcPacketHeader> {
+ let (i, fraghdr) = verify(parse_bits, |v: &(u8,u32)| v.1 >= 24)(i)?;
+ let (i, xid) = be_u32(i)?;
+ let (i, msgtype) = verify(be_u32, |&v| v <= 1)(i)?;
+ let hdr = RpcPacketHeader {
+ frag_is_last: fraghdr.0 == 1,
+ frag_len: fraghdr.1,
+ xid,
+ msgtype,
+ };
+ Ok((i, hdr))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+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_size: u32,
+ pub prog_data: &'a [u8],
+}
+
+// top of request packet, just to get to procedure
+#[derive(Debug, PartialEq, Eq)]
+pub struct RpcRequestPacketPartial {
+ pub hdr: RpcPacketHeader,
+
+ pub rpcver: u32,
+ pub program: u32,
+ pub progver: u32,
+ pub procedure: u32,
+}
+
+pub fn parse_rpc_request_partial(i: &[u8]) -> IResult<&[u8], RpcRequestPacketPartial> {
+ let (i, hdr) = parse_rpc_packet_header(i)?;
+ let (i, rpcver) = be_u32(i)?;
+ let (i, program) = be_u32(i)?;
+ let (i, progver) = be_u32(i)?;
+ let (i, procedure) = be_u32(i)?;
+ let req = RpcRequestPacketPartial {
+ hdr,
+ rpcver,
+ program,
+ progver,
+ procedure,
+ };
+ Ok((i, req))
+}
+
+#[derive(Debug, PartialEq, Eq)]
+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: RpcRequestCreds<'a>,
+
+ pub verifier_flavor: u32,
+ pub verifier_len: u32,
+ pub verifier: &'a [u8],
+
+ pub prog_data_size: u32,
+ pub prog_data: &'a [u8],
+}
+
+/// Parse request RPC record.
+///
+/// Can be called from 2 paths:
+/// 1. we have all data -> do full parsing
+/// 2. we have partial data (large records) -> allow partial prog_data parsing
+///
+/// Arguments:
+/// * `complete`:
+/// type: bool
+/// description: do full parsing, including of `prog_data`
+///
+pub fn parse_rpc(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcPacket> {
+ let (i, hdr) = parse_rpc_packet_header(start_i)?;
+ let rec_size = hdr.frag_len as usize + 4;
+
+ let (i, rpcver) = be_u32(i)?;
+ let (i, program) = be_u32(i)?;
+ let (i, progver) = be_u32(i)?;
+ let (i, procedure) = be_u32(i)?;
+
+ let (i, creds_flavor) = be_u32(i)?;
+ let (i, creds_len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?;
+ let (i, creds_buf) = take(creds_len as usize)(i)?;
+ let (_, creds) = match creds_flavor {
+ 1 => parse_rpc_request_creds_unix(creds_buf)?,
+ 6 => parse_rpc_request_creds_gssapi(creds_buf)?,
+ _ => parse_rpc_request_creds_unknown(creds_buf)?,
+ };
+
+ let (i, verifier_flavor) = be_u32(i)?;
+ let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?;
+ let (i, verifier) = take(verifier_len as usize)(i)?;
+
+ let consumed = start_i.len() - i.len();
+ if consumed > rec_size {
+ return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
+ }
+
+ let data_size : u32 = (rec_size - consumed) as u32;
+ let (i, prog_data) = if !complete {
+ rest(i)?
+ } else {
+ take(data_size)(i)?
+ };
+
+ let packet = RpcPacket {
+ hdr,
+
+ rpcver,
+ program,
+ progver,
+ procedure,
+
+ creds_flavor,
+ creds_len,
+ creds,
+
+ verifier_flavor,
+ verifier_len,
+ verifier,
+
+ prog_data_size: data_size,
+ prog_data,
+ };
+ Ok((i, packet))
+}
+
+/// Parse reply RPC record.
+///
+/// Can be called from 2 paths:
+/// 1. we have all data -> do full parsing
+/// 2. we have partial data (large records) -> allow partial prog_data parsing
+///
+/// Arguments:
+/// * `complete`:
+/// type: bool
+/// description: do full parsing, including of `prog_data`
+///
+pub fn parse_rpc_reply(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcReplyPacket> {
+ let (i, hdr) = parse_rpc_packet_header(start_i)?;
+ let rec_size = hdr.frag_len + 4;
+
+ let (i, reply_state) = verify(be_u32, |&v| v <= 1)(i)?;
+
+ let (i, verifier_flavor) = be_u32(i)?;
+ let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?;
+ let (i, verifier) = cond(verifier_len > 0, take(verifier_len as usize))(i)?;
+
+ let (i, accept_state) = be_u32(i)?;
+
+ let consumed = start_i.len() - i.len();
+ if consumed > rec_size as usize {
+ return Err(Err::Error(make_error(i, ErrorKind::LengthValue)));
+ }
+
+ let data_size : u32 = (rec_size as usize - consumed) as u32;
+ let (i, prog_data) = if !complete {
+ rest(i)?
+ } else {
+ take(data_size)(i)?
+ };
+ let packet = RpcReplyPacket {
+ hdr,
+
+ verifier_flavor,
+ verifier_len,
+ verifier,
+
+ reply_state,
+ accept_state,
+
+ prog_data_size: data_size,
+ prog_data,
+ };
+ Ok((i, packet))
+}
+
+pub fn parse_rpc_udp_packet_header(i: &[u8]) -> IResult<&[u8], RpcPacketHeader> {
+ let (i, xid) = be_u32(i)?;
+ let (i, msgtype) = verify(be_u32, |&v| v <= 1)(i)?;
+ let hdr = RpcPacketHeader {
+ frag_is_last: false,
+ frag_len: 0,
+
+ xid,
+ msgtype,
+ };
+ Ok((i, hdr))
+}
+
+pub fn parse_rpc_udp_request(i: &[u8]) -> IResult<&[u8], RpcPacket> {
+ let (i, hdr) = parse_rpc_udp_packet_header(i)?;
+
+ let (i, rpcver) = be_u32(i)?;
+ let (i, program) = be_u32(i)?;
+ let (i, progver) = be_u32(i)?;
+ let (i, procedure) = be_u32(i)?;
+
+ let (i, creds_flavor) = be_u32(i)?;
+ let (i, creds_len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?;
+ let (i, creds_buf) = take(creds_len as usize)(i)?;
+ let (_, creds) = match creds_flavor {
+ 1 => parse_rpc_request_creds_unix(creds_buf)?,
+ 6 => parse_rpc_request_creds_gssapi(creds_buf)?,
+ _ => parse_rpc_request_creds_unknown(creds_buf)?,
+ };
+
+ let (i, verifier_flavor) = be_u32(i)?;
+ let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?;
+ let (i, verifier) = take(verifier_len as usize)(i)?;
+
+ let data_size : u32 = i.len() as u32;
+ let (i, prog_data) = rest(i)?;
+ let packet = RpcPacket {
+ hdr,
+
+ rpcver,
+ program,
+ progver,
+ procedure,
+
+ creds_flavor,
+ creds_len,
+ creds,
+
+ verifier_flavor,
+ verifier_len,
+ verifier,
+
+ prog_data_size: data_size,
+ prog_data,
+ };
+ Ok((i, packet))
+}
+
+pub fn parse_rpc_udp_reply(i: &[u8]) -> IResult<&[u8], RpcReplyPacket> {
+ let (i, hdr) = parse_rpc_udp_packet_header(i)?;
+
+ let (i, verifier_flavor) = be_u32(i)?;
+ let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?;
+ let (i, verifier) = cond(verifier_len > 0, take(verifier_len as usize))(i)?;
+
+ let (i, reply_state) = verify(be_u32, |&v| v <= 1)(i)?;
+ let (i, accept_state) = be_u32(i)?;
+
+ let data_size : u32 = i.len() as u32;
+ let (i, prog_data) = rest(i)?;
+ let packet = RpcReplyPacket {
+ hdr,
+
+ verifier_flavor,
+ verifier_len,
+ verifier,
+
+ reply_state,
+ accept_state,
+
+ prog_data_size: data_size,
+ prog_data,
+ };
+ Ok((i, packet))
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::nfs::rpc_records::*;
+ use nom7::Err::Incomplete;
+ use nom7::Needed;
+
+ #[test]
+ fn test_partial_input_too_short() {
+ let buf: &[u8] = &[
+ 0x80, 0x00, 0x00, 0x9c, // flags
+ 0x8e, 0x28, 0x02, 0x7e // xid
+ ];
+
+ let r = parse_rpc_request_partial(buf);
+ match r {
+ Err(Incomplete(s)) => { assert_eq!(s, Needed::new(4)); },
+ _ => { panic!("failed {:?}",r); }
+ }
+ }
+ #[test]
+ fn test_partial_input_ok() {
+ let buf: &[u8] = &[
+ 0x80, 0x00, 0x00, 0x9c, // flags
+ 0x8e, 0x28, 0x02, 0x7e, // xid
+ 0x00, 0x00, 0x00, 0x01, // msgtype
+ 0x00, 0x00, 0x00, 0x02, // rpcver
+ 0x00, 0x00, 0x00, 0x03, // program
+ 0x00, 0x00, 0x00, 0x04, // progver
+ 0x00, 0x00, 0x00, 0x05, // procedure
+ ];
+ let expected = RpcRequestPacketPartial {
+ hdr: RpcPacketHeader {
+ frag_is_last: true,
+ frag_len: 156,
+ xid: 2384986750,
+ msgtype: 1
+ },
+ rpcver: 2,
+ program: 3,
+ progver: 4,
+ procedure: 5
+ };
+ let r = parse_rpc_request_partial(buf);
+ match r {
+ Ok((rem, hdr)) => {
+ assert_eq!(rem.len(), 0);
+ assert_eq!(hdr, expected);
+ },
+ _ => { panic!("failed {:?}",r); }
+ }
+ }
+}
diff --git a/rust/src/nfs/types.rs b/rust/src/nfs/types.rs
new file mode 100644
index 0000000..96195b9
--- /dev/null
+++ b/rust/src/nfs/types.rs
@@ -0,0 +1,293 @@
+/* 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()
+}
+
+pub const RPCMSG_ACCEPTED: u32 = 0;
+pub const RPCMSG_DENIED: u32 = 1;
+
+pub fn rpc_status_string(status: u32) -> String {
+ match status {
+ RPCMSG_ACCEPTED => "ACCEPTED",
+ RPCMSG_DENIED => "DENIED",
+ _ => {
+ return (status).to_string();
+ },
+ }.to_string()
+}
+
+/* http://www.iana.org/assignments/rpc-authentication-numbers/rpc-authentication-numbers.xhtml */
+/* RFC 1057 Section 7.2 */
+/* RFC 2203 Section 3 */
+
+pub const RPCAUTH_NULL: u32 = 0;
+pub const RPCAUTH_UNIX: u32 = 1;
+pub const RPCAUTH_SHORT: u32 = 2;
+pub const RPCAUTH_DH: u32 = 3;
+pub const RPCAUTH_KERB: u32 = 4;
+pub const RPCAUTH_RSA: u32 = 5;
+pub const RPCAUTH_GSS: u32 = 6;
+
+pub fn rpc_auth_type_string(auth_type: u32) -> String {
+ match auth_type {
+ RPCAUTH_NULL => "NULL",
+ RPCAUTH_UNIX => "UNIX",
+ RPCAUTH_SHORT => "SHORT",
+ RPCAUTH_DH => "DH",
+ RPCAUTH_KERB => "KERB",
+ RPCAUTH_RSA => "RSA",
+ RPCAUTH_GSS => "GSS",
+ _ => {
+ return (auth_type).to_string();
+ },
+ }.to_string()
+}
+
+pub fn rpc_auth_type_known(auth_type: u32) -> i8 {
+ // RPCAUTH_GSS is the maximum
+ if auth_type <= RPCAUTH_GSS {
+ return 1;
+ }
+ return -1;
+}
+
+
+pub const NFSPROC4_NULL: u32 = 0;
+pub const NFSPROC4_COMPOUND: u32 = 1;
+/* ops */
+pub const NFSPROC4_ACCESS: u32 = 3;
+pub const NFSPROC4_CLOSE: u32 = 4;
+pub const NFSPROC4_COMMIT: u32 = 5;
+pub const NFSPROC4_CREATE: u32 = 6;
+pub const NFSPROC4_DELEGPURGE: u32 = 7;
+pub const NFSPROC4_DELEGRETURN: u32 = 8;
+pub const NFSPROC4_GETATTR: u32 = 9;
+pub const NFSPROC4_GETFH: u32 = 10;
+pub const NFSPROC4_LINK: u32 = 11;
+pub const NFSPROC4_LOCK: u32 = 12;
+pub const NFSPROC4_LOCKT: u32 = 13;
+pub const NFSPROC4_LOCKU: u32 = 14;
+pub const NFSPROC4_LOOKUP: u32 = 15;
+pub const NFSPROC4_LOOKUPP: u32 = 16;
+pub const NFSPROC4_NVERIFY: u32 = 17;
+pub const NFSPROC4_OPEN: u32 = 18;
+pub const NFSPROC4_OPENATTR: u32 = 19;
+pub const NFSPROC4_OPEN_CONFIRM: u32 = 20;
+pub const NFSPROC4_OPEN_DOWNGRADE: u32 = 21;
+pub const NFSPROC4_PUTFH: u32 = 22;
+pub const NFSPROC4_PUTPUBFH: u32 = 23;
+pub const NFSPROC4_PUTROOTFH: u32 = 24;
+pub const NFSPROC4_READ: u32 = 25;
+pub const NFSPROC4_READDIR: u32 = 26;
+pub const NFSPROC4_READLINK: u32 = 27;
+pub const NFSPROC4_REMOVE: u32 = 28;
+pub const NFSPROC4_RENAME: u32 = 29;
+pub const NFSPROC4_RENEW: u32 = 30;
+pub const NFSPROC4_RESTOREFH: u32 = 31;
+pub const NFSPROC4_SAVEFH: u32 = 32;
+pub const NFSPROC4_SECINFO: u32 = 33;
+pub const NFSPROC4_SETATTR: u32 = 34;
+pub const NFSPROC4_SETCLIENTID: u32 = 35;
+pub const NFSPROC4_SETCLIENTID_CONFIRM: u32 = 36;
+pub const NFSPROC4_VERIFY: u32 = 37;
+pub const NFSPROC4_WRITE: u32 = 38;
+pub const NFSPROC4_RELEASE_LOCKOWNER: u32 = 39;
+pub const NFSPROC4_EXCHANGE_ID: u32 = 42;
+pub const NFSPROC4_CREATE_SESSION: u32 = 43;
+pub const NFSPROC4_DESTROY_SESSION: u32 = 44;
+pub const NFSPROC4_GETDEVINFO: u32 = 47;
+pub const NFSPROC4_LAYOUTGET: u32 = 50;
+pub const NFSPROC4_LAYOUTRETURN: u32 = 51;
+pub const NFSPROC4_SECINFO_NO_NAME: u32 = 52;
+pub const NFSPROC4_SEQUENCE: u32 = 53;
+pub const NFSPROC4_DESTROY_CLIENTID: u32 = 57;
+pub const NFSPROC4_RECLAIM_COMPLETE: u32 = 58;
+
+pub const NFSPROC4_ILLEGAL: u32 = 10044;
+
+
+pub fn nfs4_procedure_string(procedure: u32) -> String {
+ match procedure {
+ NFSPROC4_COMPOUND => "COMPOUND",
+ NFSPROC4_NULL => "NULL",
+ // ops
+ NFSPROC4_ACCESS => "ACCESS",
+ NFSPROC4_CLOSE => "CLOSE",
+ NFSPROC4_COMMIT => "COMMIT",
+ NFSPROC4_CREATE => "CREATE",
+ NFSPROC4_DELEGPURGE => "DELEGPURGE",
+ NFSPROC4_DELEGRETURN => "DELEGRETURN",
+ NFSPROC4_GETATTR => "GETATTR",
+ NFSPROC4_GETFH => "GETFH",
+ NFSPROC4_LINK => "LINK",
+ NFSPROC4_LOCK => "LOCK",
+ NFSPROC4_LOCKT => "LOCKT",
+ NFSPROC4_LOCKU => "LOCKU",
+ NFSPROC4_LOOKUP => "LOOKUP",
+ NFSPROC4_LOOKUPP => "LOOKUPP",
+ NFSPROC4_NVERIFY => "NVERIFY",
+ NFSPROC4_OPEN => "OPEN",
+ NFSPROC4_OPENATTR => "OPENATTR",
+ NFSPROC4_OPEN_CONFIRM => "OPEN_CONFIRM",
+ NFSPROC4_OPEN_DOWNGRADE => "OPEN_DOWNGRADE",
+ NFSPROC4_PUTFH => "PUTFH",
+ NFSPROC4_PUTPUBFH => "PUTPUBFH",
+ NFSPROC4_PUTROOTFH => "PUTROOTFH",
+ NFSPROC4_READ => "READ",
+ NFSPROC4_READDIR => "READDIR",
+ NFSPROC4_READLINK => "READLINK",
+ NFSPROC4_REMOVE => "REMOVE",
+ NFSPROC4_RENAME => "RENAME",
+ NFSPROC4_RENEW => "RENEW",
+ NFSPROC4_RESTOREFH => "RESTOREFH",
+ NFSPROC4_SAVEFH => "SAVEFH",
+ NFSPROC4_SECINFO => "SECINFO",
+ NFSPROC4_SETATTR => "SETATTR",
+ NFSPROC4_SETCLIENTID => "SETCLIENTID",
+ NFSPROC4_SETCLIENTID_CONFIRM => "SETCLIENTID_CONFIRM",
+ NFSPROC4_VERIFY => "VERIFY",
+ NFSPROC4_WRITE => "WRITE",
+ NFSPROC4_RELEASE_LOCKOWNER => "RELEASE_LOCKOWNER",
+ NFSPROC4_ILLEGAL => "ILLEGAL",
+ _ => {
+ return (procedure).to_string();
+ }
+ }.to_string()
+}
+
+pub const NFS4_OK: u32 = 0;
+