diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/nfs | |
parent | Initial commit. (diff) | |
download | suricata-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.rs | 180 | ||||
-rw-r--r-- | rust/src/nfs/mod.rs | 33 | ||||
-rw-r--r-- | rust/src/nfs/nfs.rs | 2121 | ||||
-rw-r--r-- | rust/src/nfs/nfs2.rs | 122 | ||||
-rw-r--r-- | rust/src/nfs/nfs2_records.rs | 235 | ||||
-rw-r--r-- | rust/src/nfs/nfs3.rs | 274 | ||||
-rw-r--r-- | rust/src/nfs/nfs3_records.rs | 1000 | ||||
-rw-r--r-- | rust/src/nfs/nfs4.rs | 425 | ||||
-rw-r--r-- | rust/src/nfs/nfs4_records.rs | 2074 | ||||
-rw-r--r-- | rust/src/nfs/nfs_records.rs | 29 | ||||
-rw-r--r-- | rust/src/nfs/rpc_records.rs | 465 | ||||
-rw-r--r-- | rust/src/nfs/types.rs | 293 |
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; + |