diff --git a/rust/src/nfs/log.rs b/rust/src/nfs/log.rs index c1071419d0..1f5bde8b71 100644 --- a/rust/src/nfs/log.rs +++ b/rust/src/nfs/log.rs @@ -94,7 +94,12 @@ fn nfs_common_header(state: &NFSState, tx: &NFSTransaction) -> Json { let js = Json::object(); js.set_integer("version", state.nfs_version as u64); - js.set_string("procedure", &nfs3_procedure_string(tx.procedure)); + 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); diff --git a/rust/src/nfs/mod.rs b/rust/src/nfs/mod.rs index b9b7970836..5638b0c8cb 100644 --- a/rust/src/nfs/mod.rs +++ b/rust/src/nfs/mod.rs @@ -20,6 +20,8 @@ pub mod rpc_records; pub mod nfs_records; pub mod nfs2_records; pub mod nfs3_records; +pub mod nfs4_records; +pub mod nfs4; pub mod nfs; pub mod log; diff --git a/rust/src/nfs/nfs.rs b/rust/src/nfs/nfs.rs index c9e8e7548e..643af76279 100644 --- a/rust/src/nfs/nfs.rs +++ b/rust/src/nfs/nfs.rs @@ -155,8 +155,8 @@ pub struct NFSTransaction { /// for state tracking. false means this side is in progress, true /// that it's complete. - request_done: bool, - response_done: bool, + pub request_done: bool, + pub response_done: bool, pub nfs_version: u16, @@ -231,13 +231,13 @@ impl Drop for NFSTransaction { #[derive(Debug)] pub struct NFSRequestXidMap { - progver: u32, - procedure: u32, - chunk_offset: u64, - file_name:Vec, + pub progver: u32, + pub procedure: u32, + pub chunk_offset: u64, + pub file_name:Vec, /// READ replies can use this to get to the handle the request used - file_handle:Vec, + pub file_handle:Vec, } impl NFSRequestXidMap { @@ -285,7 +285,7 @@ impl NFSFiles { } /// little wrapper around the FileTransferTracker::new_chunk method -fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer, +pub fn filetracker_newchunk(ft: &mut FileTransferTracker, files: &mut FileContainer, flags: u16, name: &Vec, data: &[u8], chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32) { @@ -315,11 +315,11 @@ pub struct NFSState { pub files: NFSFiles, /// partial record tracking - ts_chunk_xid: u32, - tc_chunk_xid: u32, + pub ts_chunk_xid: u32, + pub tc_chunk_xid: u32, /// size of the current chunk that we still need to receive - ts_chunk_left: u32, - tc_chunk_left: u32, + pub ts_chunk_left: u32, + pub tc_chunk_left: u32, ts_ssn_gap: bool, tc_ssn_gap: bool, @@ -483,7 +483,7 @@ impl NFSState { }; } - fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) { + pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) { match self.namemap.get(&xidmap.file_handle) { Some(n) => { SCLogDebug!("xidmap_handle2name: name {:?}", n); @@ -501,6 +501,10 @@ impl NFSState { SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}", r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); + if r.progver == 4 { + return self.process_request_record_v4(r); + } + let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); let mut aux_file_name = Vec::new(); @@ -779,7 +783,7 @@ impl NFSState { 0 } - fn new_file_tx(&mut self, file_handle: &Vec, file_name: &Vec, direction: u8) + pub fn new_file_tx(&mut self, file_handle: &Vec, file_name: &Vec, direction: u8) -> (&mut NFSTransaction, &mut FileContainer, u16) { let mut tx = self.new_tx(); @@ -803,7 +807,7 @@ impl NFSState { return (tx_ref.unwrap(), files, flags) } - fn get_file_tx_by_handle(&mut self, file_handle: &Vec, direction: u8) + pub fn get_file_tx_by_handle(&mut self, file_handle: &Vec, direction: u8) -> Option<(&mut NFSTransaction, &mut FileContainer, u16)> { let fh = file_handle.to_vec(); @@ -1080,9 +1084,8 @@ impl NFSState { return self.process_reply_record_v3(r, &mut xidmap); }, 4 => { - SCLogDebug!("NFSv4 unsupported"); - self.set_event(NFSEvent::UnsupportedVersion); - return 0; + SCLogDebug!("NFSv4 reply record"); + return self.process_reply_record_v4(r, &mut xidmap); }, _ => { SCLogDebug!("Invalid NFS version"); @@ -1199,7 +1202,7 @@ impl NFSState { /// xidmapr is an Option as it's already removed from the map if we /// have a complete record. Otherwise we do a lookup ourselves. - fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>, + pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32 { let file_name; @@ -1226,6 +1229,7 @@ impl NFSState { } }, } + SCLogDebug!("chunk_offset {}", chunk_offset); let mut is_last = reply.eof; let mut fill_bytes = 0; @@ -1233,7 +1237,8 @@ impl NFSState { if pad != 0 { fill_bytes = 4 - pad; } - SCLogDebug!("XID {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}", r.hdr.xid, fill_bytes, reply.count, reply.data_len, reply.data.len()); + 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) { @@ -1256,6 +1261,7 @@ impl NFSState { let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOCLIENT) { Some((tx, files, flags)) => { + SCLogDebug!("updated TX {:?}", tx); let ref mut tdf = match tx.type_data { Some(NFSTransactionTypeData::FILE(ref mut x)) => x, _ => { panic!("BUG") }, @@ -1291,7 +1297,7 @@ impl NFSState { filetracker_newchunk(&mut tdf.file_tracker, files, flags, &file_name, reply.data, chunk_offset, reply.count, fill_bytes as u8, is_last, &r.hdr.xid); - tx.procedure = NFSPROC3_READ; + tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ }; tx.xid = r.hdr.xid; tx.is_first = true; if is_last { @@ -2106,7 +2112,8 @@ pub fn nfs3_probe(i: &[u8], direction: u8) -> i8 { match parse_rpc(i) { IResult::Done(_, ref rpc) => { if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 && - rpc.rpcver == 2 && rpc.progver == 3 && rpc.program == 100003 && + rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) && + rpc.program == 100003 && rpc.procedure <= NFSPROC3_COMMIT { return 1; diff --git a/rust/src/nfs/nfs4.rs b/rust/src/nfs/nfs4.rs new file mode 100644 index 0000000000..480fd36453 --- /dev/null +++ b/rust/src/nfs/nfs4.rs @@ -0,0 +1,250 @@ +/* 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. + */ + +// written by Victor Julien + +extern crate libc; + +use nom::IResult; + +use core::*; +use log::*; + +use nfs::nfs::*; +use nfs::types::*; +use nfs::rpc_records::*; +use nfs::nfs_records::*; +use nfs::nfs4_records::*; + +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 = if w.stable == 2 { true } else { false }; + SCLogDebug!("is_last {}", is_last); + + let mut fill_bytes = 0; + let pad = w.write_len % 4; + if pad != 0 { + fill_bytes = 4 - pad; + } + + let file_handle = fh.to_vec(); + let file_name = match self.namemap.get(fh) { + Some(n) => { + SCLogDebug!("WRITE name {:?}", n); + n.to_vec() + }, + None => { + SCLogDebug!("WRITE object {:?} not found", w.stateid.data); + Vec::new() + }, + }; + + let found = match self.get_file_tx_by_handle(&file_handle, STREAM_TOSERVER) { + Some((tx, files, flags)) => { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &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, files, flags) = self.new_file_tx(&file_handle, &file_name, STREAM_TOSERVER); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, files, flags, + &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; + } + } + } + self.ts_chunk_xid = r.hdr.xid; + let file_data_len = w.data.len() as u32 - fill_bytes as u32; + self.ts_chunk_left = w.write_len as u32 - file_data_len as u32; + } + + /* 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; + + for c in &cr.commands { + SCLogDebug!("c {:?}", c); + match c { + &Nfs4RequestContent::PutFH(ref rd) => { + last_putfh = Some(rd.value); + } + &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::Close(ref rd) => { + SCLogDebug!("CLOSEv4: {:?}", rd); + } + &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)); + } + &_ => { }, + } + } + } + + /// complete request record + pub fn process_request_record_v4<'b>(&mut self, r: &RpcPacket<'b>) -> u32 { + 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_COMPOUND { + match parse_nfs4_request_compound(r.prog_data) { + IResult::Done(_, rd) => { + SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd); + self.compound_request(&r, &rd, &mut xidmap); + }, + IResult::Incomplete(_n) => { + SCLogNotice!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n); + self.set_event(NFSEvent::MalformedData); + }, + IResult::Error(e) => { panic!("Parsing failed: {:?}",e); }, + }; + } + + self.requestmap.insert(r.hdr.xid, xidmap); + 0 + } + + fn compound_response<'b>(&mut self, r: &RpcReplyPacket<'b>, + cr: &Nfs4ResponseCompoundRecord<'b>, + xidmap: &mut NFSRequestXidMap) + { + let mut insert_filename_with_getfh = false; + + for c in &cr.commands { + SCLogDebug!("c {:?}", c); + match c { + &Nfs4ResponseContent::ReadDir(s, ref rd) => { + if let &Some(ref rd) = rd { + SCLogDebug!("READDIRv4: status {} eof {}", s, rd.eof); + + for d in &rd.listing { + if let &Some(ref d) = d { + SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(&d.name)); + } + } + + } + } + &Nfs4ResponseContent::Remove(s) => { + SCLogDebug!("REMOVE4: status {}", s); + }, + &Nfs4ResponseContent::Read(s, ref rd) => { + if let &Some(ref rd) = 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, ref rd) => { + if let &Some(ref rd) = rd { + SCLogDebug!("OPENv4: status {} opendata {:?}", s, rd); + insert_filename_with_getfh = true; + } + }, + &Nfs4ResponseContent::GetFH(_s, ref rd) => { + if let &Some(ref rd) = rd { + if insert_filename_with_getfh { + self.namemap.insert(rd.value.to_vec(), + xidmap.file_name.to_vec()); + } + } + }, + &_ => { }, + } + } + } + + pub fn process_reply_record_v4<'b>(&mut self, r: &RpcReplyPacket<'b>, + xidmap: &mut NFSRequestXidMap) -> u32 { + if xidmap.procedure == NFSPROC4_COMPOUND { + match parse_nfs4_response_compound(r.prog_data) { + IResult::Done(_, rd) => { + SCLogDebug!("COMPOUNDv4: {:?}", rd); + self.compound_response(&r, &rd, xidmap); + }, + IResult::Incomplete(_) => { + self.set_event(NFSEvent::MalformedData); + }, + IResult::Error(e) => { panic!("Parsing failed: {:?}",e); }, + }; + } + 0 + } +} diff --git a/rust/src/nfs/nfs4_records.rs b/rust/src/nfs/nfs4_records.rs new file mode 100644 index 0000000000..70538652d6 --- /dev/null +++ b/rust/src/nfs/nfs4_records.rs @@ -0,0 +1,843 @@ +/* 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 nom::{be_u32, be_u64}; + +use nfs::types::*; + +#[derive(Debug,PartialEq)] +pub enum Nfs4RequestContent<'a> { + PutFH(Nfs4Handle<'a>), + GetFH, + SaveFH, + PutRootFH, + ReadDir, + 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>), +} + +#[derive(Debug,PartialEq)] +pub struct Nfs4Attr { + attr_mask: u64, +} + +named!(nfs4_parse_attr_fields, + do_parse!( + len: be_u32 + >> take!(len) + >> (len) +)); + +named!(nfs4_parse_attrs, + do_parse!( + attr_cnt: be_u32 + >> attr_mask1: be_u32 + >> attr_mask2: cond!(attr_cnt == 2, be_u32) + >> nfs4_parse_attr_fields + >> ( Nfs4Attr { + attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64, + } ) +)); + +named!(nfs4_parse_attrbits, + do_parse!( + attr_cnt: be_u32 + >> attr_mask1: be_u32 + >> attr_mask2: cond!(attr_cnt == 2, be_u32) + >> ( Nfs4Attr { + attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64, + } ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4StateId<'a> { + pub seqid: u32, + pub data: &'a[u8], +} + +named!(nfs4_parse_stateid, + do_parse!( + seqid: be_u32 + >> data: take!(12) + >> ( Nfs4StateId { + seqid: seqid, + data: data, + }) + ) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs4Handle<'a> { + pub len: u32, + pub value: &'a[u8], +} + +named!(nfs4_parse_handle, + do_parse!( + obj_len: be_u32 + >> obj: take!(obj_len) + >> ( Nfs4Handle { + len: obj_len, + value: obj, + }) +)); + +named!(nfs4_parse_nfsstring<&[u8]>, + do_parse!( + len: be_u32 + >> data: take!(len) + >> _fill_bytes: cond!(len % 4 != 0, take!(4 - len % 4)) + >> ( data ) +)); + +named!(nfs4_req_putfh, + do_parse!( + h: nfs4_parse_handle + >> ( Nfs4RequestContent::PutFH(h) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestSetClientId<'a> { + pub client_id: &'a[u8], + pub r_netid: &'a[u8], + pub r_addr: &'a[u8], +} + +named!(nfs4_req_setclientid, + do_parse!( + client_verifier: take!(8) + >> client_id: nfs4_parse_nfsstring + >> cb_program: be_u32 + >> r_netid: nfs4_parse_nfsstring + >> r_addr: nfs4_parse_nfsstring + >> cb_id: be_u32 + >> (Nfs4RequestContent::SetClientId(Nfs4RequestSetClientId { + client_id: client_id, + r_netid: r_netid, + r_addr: r_addr, + })) +)); + +named!(nfs4_req_setclientid_confirm, + do_parse!( + client_id: take!(8) + >> verifier: take!(8) + >> (Nfs4RequestContent::SetClientIdConfirm) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestCreate<'a> { + pub ftype4: u32, + pub filename: &'a[u8], +} + +named!(nfs4_req_create, + do_parse!( + ftype4: be_u32 + >> filename: nfs4_parse_nfsstring + >> attrs: nfs4_parse_attrs + >> ( Nfs4RequestContent::Create(Nfs4RequestCreate { + ftype4: ftype4, + filename: filename, + }) + )) +); + +#[derive(Debug,PartialEq)] +pub enum Nfs4OpenRequestContent<'a> { + Exclusive4(&'a[u8]), + Unchecked4(Nfs4Attr), + Guarded4(Nfs4Attr), +} + +named!(nfs4_req_open_unchecked4, + do_parse!( + attrs: nfs4_parse_attrs + >> ( Nfs4OpenRequestContent::Unchecked4(attrs) ) +)); + +named!(nfs4_req_open_guarded4, + do_parse!( + attrs: nfs4_parse_attrs + >> ( Nfs4OpenRequestContent::Guarded4(attrs) ) +)); + +named!(nfs4_req_open_exclusive4, + do_parse!( + ver: take!(8) + >> ( Nfs4OpenRequestContent::Exclusive4(ver) ) +)); + + +named!(nfs4_req_open_type, + do_parse!( + mode: be_u32 + >> data: switch!(value!(mode), + 0 => call!(nfs4_req_open_unchecked4) | + 1 => call!(nfs4_req_open_guarded4) | + 2 => call!(nfs4_req_open_exclusive4)) + >> ( data ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestOpen<'a> { + pub open_type: u32, + pub filename: &'a[u8], + pub open_data: Option>, +} + +named!(nfs4_req_open, + do_parse!( + seqid: be_u32 + >> share_access: be_u32 + >> share_deny: be_u32 + >> client_id: be_u64 + >> owner_len: be_u32 + >> cond!(owner_len > 0, take!(owner_len)) + >> open_type: be_u32 + >> open_data: cond!(open_type == 1, nfs4_req_open_type) + >> claim_type: be_u32 + >> filename: nfs4_parse_nfsstring + >> ( Nfs4RequestContent::Open(Nfs4RequestOpen { + open_type: open_type, + filename: filename, + open_data: open_data, + }) + )) +); + +named!(nfs4_req_readdir, + do_parse!( + cookie: be_u64 + >> cookie_verf: be_u64 + >> dir_cnt: be_u32 + >> max_cnt: be_u32 + >> attr: nfs4_parse_attrbits + >> ( Nfs4RequestContent::ReadDir ) + ) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestRename<'a> { + pub oldname: &'a[u8], + pub newname: &'a[u8], +} + +named!(nfs4_req_rename, + do_parse!( + oldname: nfs4_parse_nfsstring + >> newname: nfs4_parse_nfsstring + >> ( Nfs4RequestContent::Rename(Nfs4RequestRename { + oldname: oldname, + newname: newname, + }) + )) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestLookup<'a> { + pub filename: &'a[u8], +} + +named!(nfs4_req_lookup, + do_parse!( + filename: nfs4_parse_nfsstring + >> ( Nfs4RequestContent::Lookup(Nfs4RequestLookup { + filename: filename, + }) + )) +); + +named!(nfs4_req_remove, + do_parse!( + filename: nfs4_parse_nfsstring + >> ( Nfs4RequestContent::Remove(filename) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestSetAttr<'a> { + pub stateid: Nfs4StateId<'a>, +} + +named!(nfs4_req_setattr, + do_parse!( + stateid: nfs4_parse_stateid + >> attrs: nfs4_parse_attrs + >> (Nfs4RequestContent::SetAttr(Nfs4RequestSetAttr { + stateid: stateid, + })) +)); + +named!(nfs4_req_getattr, + do_parse!( + attrs: nfs4_parse_attrbits + >> ( Nfs4RequestContent::GetAttr(attrs) ) + ) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestWrite<'a> { + pub stateid: Nfs4StateId<'a>, + pub offset: u64, + pub stable: u32, + pub write_len: u32, + pub data: &'a[u8], +} + +named!(nfs4_req_write, + do_parse!( + stateid: nfs4_parse_stateid + >> offset: be_u64 + >> stable: be_u32 + >> write_len: be_u32 + >> data: take!(write_len) + >> _padding: cond!(write_len % 4 != 0, take!(4 - write_len % 4)) + >> (Nfs4RequestContent::Write(Nfs4RequestWrite { + stateid: stateid, + offset: offset, + stable: stable, + write_len: write_len, + data: data, + })) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestRead<'a> { + pub stateid: Nfs4StateId<'a>, + pub offset: u64, + pub count: u32, +} + +named!(nfs4_req_read, + do_parse!( + stateid: nfs4_parse_stateid + >> offset: be_u64 + >> count: be_u32 + >> ( Nfs4RequestContent::Read(Nfs4RequestRead { + stateid: stateid, + offset: offset, + count: count, + }) + )) +); + +named!(nfs4_req_close, + do_parse!( + seqid: be_u32 + >> stateid: nfs4_parse_stateid + >> ( Nfs4RequestContent::Close(stateid) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestOpenConfirm<'a> { + pub stateid: Nfs4StateId<'a>, +} + +named!(nfs4_req_open_confirm, + do_parse!( + stateid: nfs4_parse_stateid + >> seqid: be_u32 + >> ( Nfs4RequestContent::OpenConfirm(Nfs4RequestOpenConfirm { + stateid: stateid, + }) + )) +); + +named!(nfs4_req_delegreturn, + do_parse!( + a: nfs4_parse_stateid + >> ( Nfs4RequestContent::DelegReturn(a) ) + ) +); + +named!(nfs4_req_renew, + do_parse!( + a: be_u64 + >> ( Nfs4RequestContent::Renew(a) ) + ) +); + +named!(nfs4_req_getfh, + do_parse!( ( Nfs4RequestContent::GetFH ) )); + +named!(nfs4_req_savefh, + do_parse!( ( Nfs4RequestContent::SaveFH ) )); + +named!(nfs4_req_putrootfh, + do_parse!( ( Nfs4RequestContent::PutRootFH ) )); + +named!(nfs4_req_access, + do_parse!( + a: be_u32 + >> ( Nfs4RequestContent::Access(a) ) + ) +); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestExchangeId<'a> { + pub client_string: &'a[u8], + pub nii_domain: &'a[u8], + pub nii_name: &'a[u8], +} + +named!(nfs4_req_exchangeid, + do_parse!( + verifier: take!(8) + >> eia_clientstring: nfs4_parse_nfsstring + >> eia_clientflags: be_u32 + >> eia_state_protect: be_u32 + >> eia_client_impl_id: be_u32 + >> nii_domain: nfs4_parse_nfsstring + >> nii_name: nfs4_parse_nfsstring + >> nii_data_sec: be_u64 + >> nii_data_nsec: be_u32 + >> (Nfs4RequestContent::ExchangeId( + Nfs4RequestExchangeId { + client_string: eia_clientstring, + nii_domain: nii_domain, + nii_name: nii_name, + } + )) +)); + +named!(parse_request_compound_command, + do_parse!( + cmd: be_u32 + >> cmd_data: switch!(value!(cmd), + NFSPROC4_PUTFH => call!(nfs4_req_putfh) | + NFSPROC4_READ => call!(nfs4_req_read) | + NFSPROC4_WRITE => call!(nfs4_req_write) | + NFSPROC4_GETFH => call!(nfs4_req_getfh) | + NFSPROC4_SAVEFH => call!(nfs4_req_savefh) | + NFSPROC4_OPEN => call!(nfs4_req_open) | + NFSPROC4_CLOSE => call!(nfs4_req_close) | + NFSPROC4_LOOKUP => call!(nfs4_req_lookup) | + NFSPROC4_ACCESS => call!(nfs4_req_access) | + NFSPROC4_GETATTR => call!(nfs4_req_getattr) | + NFSPROC4_READDIR => call!(nfs4_req_readdir) | + NFSPROC4_RENEW => call!(nfs4_req_renew) | + NFSPROC4_OPEN_CONFIRM => call!(nfs4_req_open_confirm) | + NFSPROC4_REMOVE => call!(nfs4_req_remove) | + NFSPROC4_RENAME => call!(nfs4_req_rename) | + NFSPROC4_CREATE => call!(nfs4_req_create) | + NFSPROC4_DELEGRETURN => call!(nfs4_req_delegreturn) | + NFSPROC4_SETATTR => call!(nfs4_req_setattr) | + NFSPROC4_PUTROOTFH => call!(nfs4_req_putrootfh) | + NFSPROC4_SETCLIENTID => call!(nfs4_req_setclientid) | + NFSPROC4_SETCLIENTID_CONFIRM => call!(nfs4_req_setclientid_confirm) | + NFSPROC4_EXCHANGE_ID => call!(nfs4_req_exchangeid) + ) + >> ( cmd_data ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4RequestCompoundRecord<'a> { + pub commands: Vec>, +} + +named!(pub parse_nfs4_request_compound, + do_parse!( + tag_len: be_u32 + >> tag: cond!(tag_len > 0, take!(tag_len)) + >> min_ver: be_u32 + >> ops_cnt: be_u32 + >> commands: count!(parse_request_compound_command, ops_cnt as usize) + >> (Nfs4RequestCompoundRecord { + commands: commands, + }) +)); + +#[derive(Debug,PartialEq)] +pub enum Nfs4ResponseContent<'a> { + PutFH(u32), + PutRootFH(u32), + GetFH(u32, Option>), + Lookup(u32), + SaveFH(u32), + Rename(u32), + Write(u32, Option), + Read(u32, Option>), + Renew(u32), + Open(u32, Option>), + OpenConfirm(u32, Option>), + Close(u32, Option>), + GetAttr(u32, Option), + SetAttr(u32), + Access(u32, Option), + ReadDir(u32, Option>), + Remove(u32), + DelegReturn(u32), + SetClientId(u32), + SetClientIdConfirm(u32), + Create(u32), +} + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseWrite { + pub count: u32, + pub committed: u32, +} + +named!(nfs4_res_write_ok, + do_parse!( + count: be_u32 + >> committed: be_u32 + >> verifier: be_u64 + >> (Nfs4ResponseWrite { + count: count, + committed: committed, + }) +)); + +named!(nfs4_res_write, + do_parse!( + status: be_u32 + >> wd: cond!(status == 0, nfs4_res_write_ok) + >> (Nfs4ResponseContent::Write(status, wd) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseRead<'a> { + pub eof: bool, + pub count: u32, + pub data: &'a[u8], +} + +named!(nfs4_res_read_ok, + do_parse!( + eof: be_u32 + >> read_len: be_u32 + >> read_data: take!(read_len) + >> (Nfs4ResponseRead { + eof: eof==1, + count: read_len, + data: read_data, + }) +)); + +named!(nfs4_res_read, + do_parse!( + status: be_u32 + >> rd: cond!(status == 0, nfs4_res_read_ok) + >> (Nfs4ResponseContent::Read(status, rd) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseOpen<'a> { + pub stateid: Nfs4StateId<'a>, + pub result_flags: u32, + pub delegation_type: u32, + pub delegate_read: Option>, +} + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseOpenDelegateRead<'a> { + pub stateid: Nfs4StateId<'a>, +} + +named!(nfs4_res_open_ok_delegate_read, + do_parse!( + stateid: nfs4_parse_stateid + >> recall: be_u32 + >> ace_type: be_u32 + >> ace_flags: be_u32 + >> ace_mask: be_u32 + >> who_len: be_u32 + >> who: take!(who_len) + >> (Nfs4ResponseOpenDelegateRead { + stateid: stateid, + }) +)); + +named!(nfs4_res_open_ok, + do_parse!( + stateid: nfs4_parse_stateid + >> change_info: take!(20) + >> result_flags: be_u32 + >> attrs: nfs4_parse_attrbits + >> delegation_type: be_u32 + >> delegate_read: cond!(delegation_type == 1, nfs4_res_open_ok_delegate_read) + >> ( Nfs4ResponseOpen { + stateid: stateid, result_flags: result_flags, + delegation_type: delegation_type, + delegate_read: delegate_read, + } ) +)); + +named!(nfs4_res_open, + do_parse!( + status: be_u32 + >> open_data: cond!(status == 0, nfs4_res_open_ok) + >> ( Nfs4ResponseContent::Open(status, open_data) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseReaddirEntry<'a> { + pub name: &'a[u8], +} + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseReaddir<'a> { + pub eof: bool, + pub listing: Vec>>, +} + +named!(nfs4_res_readdir_entry_do, + do_parse!( + cookie: be_u64 + >> name: nfs4_parse_nfsstring + >> attrs: nfs4_parse_attrs + >> ( Nfs4ResponseReaddirEntry { + name: name, + }) +)); + +named!(nfs4_res_readdir_entry>, + do_parse!( + value_follows: be_u32 + >> entry: cond!(value_follows == 1, nfs4_res_readdir_entry_do) + >> (entry) +)); + +named!(nfs4_res_readdir_ok, + do_parse!( + verifier: be_u64 + // run parser until we find a 'value follows == 0' + >> listing: many_till!(call!(nfs4_res_readdir_entry), peek!(tag!(b"\x00\x00\x00\x00"))) + // value follows == 0 checked by line above + >> _value_follows: be_u32 + >> eof: be_u32 + >> ( Nfs4ResponseReaddir { eof: eof==1, listing: listing.0 }) +)); + +named!(nfs4_res_readdir, + do_parse!( + status: be_u32 + >> rd: cond!(status == 0, nfs4_res_readdir_ok) + >> ( Nfs4ResponseContent::ReadDir(status, rd) ) +)); + +named!(nfs4_res_create_ok, + do_parse!( + change_info: take!(20) + >> attrs: nfs4_parse_attrbits + >> ( attrs ) +)); + +named!(nfs4_res_create, + do_parse!( + status: be_u32 + >> attrs: cond!(status == 0, nfs4_res_create_ok) + >> ( Nfs4ResponseContent::Create(status) ) +)); + +named!(nfs4_res_setattr_ok, + do_parse!( + attrs: nfs4_parse_attrbits + >> ( attrs ) +)); + +named!(nfs4_res_setattr, + do_parse!( + status: be_u32 + >> attrs: cond!(status == 0, nfs4_res_setattr_ok) + >> ( Nfs4ResponseContent::SetAttr(status) ) +)); + +named!(nfs4_res_getattr_ok, + do_parse!( + attrs: nfs4_parse_attrs + >> ( attrs ) +)); + +named!(nfs4_res_getattr, + do_parse!( + status: be_u32 + >> attrs: cond!(status == 0, nfs4_res_getattr_ok) + >> ( Nfs4ResponseContent::GetAttr(status, attrs) ) +)); + +named!(nfs4_res_openconfirm, + do_parse!( + status: be_u32 + >> stateid: cond!(status == 0, nfs4_parse_stateid) + >> ( Nfs4ResponseContent::OpenConfirm(status, stateid) ) +)); + +named!(nfs4_res_close, + do_parse!( + status: be_u32 + >> stateid: cond!(status == 0, nfs4_parse_stateid) + >> ( Nfs4ResponseContent::Close(status, stateid) ) +)); + +named!(nfs4_res_remove, + do_parse!( + status: be_u32 + >> cond!(status == 0, take!(20)) // change_info + >> ( Nfs4ResponseContent::Remove(status) ) +)); + +named!(nfs4_res_rename, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::Rename(status) ) +)); + +named!(nfs4_res_savefh, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::SaveFH(status) ) +)); + +named!(nfs4_res_lookup, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::Lookup(status) ) +)); + +named!(nfs4_res_renew, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::Renew(status) ) +)); + +named!(nfs4_res_getfh, + do_parse!( + status: be_u32 + >> fh: cond!(status == 0, nfs4_parse_handle) + >> ( Nfs4ResponseContent::GetFH(status, fh) ) +)); + +named!(nfs4_res_putfh, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::PutFH(status) ) +)); + +named!(nfs4_res_putrootfh, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::PutRootFH(status) ) +)); + +named!(nfs4_res_delegreturn, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::DelegReturn(status) ) +)); + +named!(nfs4_res_setclientid, + do_parse!( + status: be_u32 + >> client_id: be_u64 + >> verifier: be_u32 + >> ( Nfs4ResponseContent::SetClientId(status) ) +)); + +named!(nfs4_res_setclientid_confirm, + do_parse!( + status: be_u32 + >> ( Nfs4ResponseContent::SetClientIdConfirm(status) ) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseAccess { + pub supported_types: u32, + pub access_rights: u32, +} + +named!(nfs4_res_access_ok, + do_parse!( + s: be_u32 + >> a: be_u32 + >> (Nfs4ResponseAccess { + supported_types: s, + access_rights: a, + }) +)); + +named!(nfs4_res_access, + do_parse!( + status: be_u32 + >> ad: cond!(status == 0, nfs4_res_access_ok) + >> ( Nfs4ResponseContent::Access( + status, ad, )) +)); + +named!(nfs4_res_compound_command, + do_parse!( + cmd: be_u32 + >> cmd_data: switch!(value!(cmd), + NFSPROC4_READ => call!(nfs4_res_read) | + NFSPROC4_WRITE => call!(nfs4_res_write) | + NFSPROC4_ACCESS => call!(nfs4_res_access) | + NFSPROC4_GETFH => call!(nfs4_res_getfh) | + NFSPROC4_PUTFH => call!(nfs4_res_putfh) | + NFSPROC4_SAVEFH => call!(nfs4_res_savefh) | + NFSPROC4_RENAME => call!(nfs4_res_rename) | + NFSPROC4_READDIR => call!(nfs4_res_readdir) | + NFSPROC4_GETATTR => call!(nfs4_res_getattr) | + NFSPROC4_SETATTR => call!(nfs4_res_setattr) | + NFSPROC4_LOOKUP => call!(nfs4_res_lookup) | + NFSPROC4_OPEN => call!(nfs4_res_open) | + NFSPROC4_OPEN_CONFIRM => call!(nfs4_res_openconfirm) | + NFSPROC4_CLOSE => call!(nfs4_res_close) | + NFSPROC4_REMOVE => call!(nfs4_res_remove) | + NFSPROC4_CREATE => call!(nfs4_res_create) | + NFSPROC4_DELEGRETURN => call!(nfs4_res_delegreturn) | + NFSPROC4_SETCLIENTID => call!(nfs4_res_setclientid) | + NFSPROC4_SETCLIENTID_CONFIRM => call!(nfs4_res_setclientid_confirm) | + NFSPROC4_PUTROOTFH => call!(nfs4_res_putrootfh) | + NFSPROC4_RENEW => call!(nfs4_res_renew)) + >> (cmd_data) +)); + +#[derive(Debug,PartialEq)] +pub struct Nfs4ResponseCompoundRecord<'a> { + pub status: u32, + pub commands: Vec>, +} + +named!(pub parse_nfs4_response_compound, + do_parse!( + status: be_u32 + >> tag_len: be_u32 + >> tag: cond!(tag_len > 0, take!(tag_len)) + >> ops_cnt: be_u32 + >> commands: count!(nfs4_res_compound_command, ops_cnt as usize) + >> (Nfs4ResponseCompoundRecord { + status: status, + commands: commands, + }) +)); diff --git a/rust/src/nfs/types.rs b/rust/src/nfs/types.rs index b0a0f88db9..bc1a68b92f 100644 --- a/rust/src/nfs/types.rs +++ b/rust/src/nfs/types.rs @@ -224,3 +224,99 @@ pub fn rpc_auth_status_string(auth_status: u32) -> String { }, }.to_string() } + +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_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() +}