/// 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,
#[derive(Debug)]
pub struct NFSRequestXidMap {
- progver: u32,
- procedure: u32,
- chunk_offset: u64,
- file_name:Vec<u8>,
+ 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
- file_handle:Vec<u8>,
+ pub file_handle:Vec<u8>,
}
impl NFSRequestXidMap {
}
/// 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<u8>, data: &[u8],
chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32)
{
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,
};
}
- 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);
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();
0
}
- fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
+ pub fn new_file_tx(&mut self, file_handle: &Vec<u8>, file_name: &Vec<u8>, direction: u8)
-> (&mut NFSTransaction, &mut FileContainer, u16)
{
let mut tx = self.new_tx();
return (tx_ref.unwrap(), files, flags)
}
- fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8)
+ pub fn get_file_tx_by_handle(&mut self, file_handle: &Vec<u8>, direction: u8)
-> Option<(&mut NFSTransaction, &mut FileContainer, u16)>
{
let fh = file_handle.to_vec();
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");
/// 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;
}
},
}
+ SCLogDebug!("chunk_offset {}", chunk_offset);
let mut is_last = reply.eof;
let mut fill_bytes = 0;
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) {
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") },
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 {
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;
--- /dev/null
+/* 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
+ }
+}
--- /dev/null
+/* 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<u32>,
+ do_parse!(
+ len: be_u32
+ >> take!(len)
+ >> (len)
+));
+
+named!(nfs4_parse_attrs<Nfs4Attr>,
+ 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<Nfs4Attr>,
+ 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<Nfs4StateId>,
+ 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<Nfs4Handle>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4OpenRequestContent>,
+ do_parse!(
+ attrs: nfs4_parse_attrs
+ >> ( Nfs4OpenRequestContent::Unchecked4(attrs) )
+));
+
+named!(nfs4_req_open_guarded4<Nfs4OpenRequestContent>,
+ do_parse!(
+ attrs: nfs4_parse_attrs
+ >> ( Nfs4OpenRequestContent::Guarded4(attrs) )
+));
+
+named!(nfs4_req_open_exclusive4<Nfs4OpenRequestContent>,
+ do_parse!(
+ ver: take!(8)
+ >> ( Nfs4OpenRequestContent::Exclusive4(ver) )
+));
+
+
+named!(nfs4_req_open_type<Nfs4OpenRequestContent>,
+ 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<Nfs4OpenRequestContent<'a>>,
+}
+
+named!(nfs4_req_open<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ do_parse!(
+ filename: nfs4_parse_nfsstring
+ >> ( Nfs4RequestContent::Lookup(Nfs4RequestLookup {
+ filename: filename,
+ })
+ ))
+);
+
+named!(nfs4_req_remove<Nfs4RequestContent>,
+ do_parse!(
+ filename: nfs4_parse_nfsstring
+ >> ( Nfs4RequestContent::Remove(filename) )
+));
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4RequestSetAttr<'a> {
+ pub stateid: Nfs4StateId<'a>,
+}
+
+named!(nfs4_req_setattr<Nfs4RequestContent>,
+ do_parse!(
+ stateid: nfs4_parse_stateid
+ >> attrs: nfs4_parse_attrs
+ >> (Nfs4RequestContent::SetAttr(Nfs4RequestSetAttr {
+ stateid: stateid,
+ }))
+));
+
+named!(nfs4_req_getattr<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ do_parse!(
+ stateid: nfs4_parse_stateid
+ >> seqid: be_u32
+ >> ( Nfs4RequestContent::OpenConfirm(Nfs4RequestOpenConfirm {
+ stateid: stateid,
+ })
+ ))
+);
+
+named!(nfs4_req_delegreturn<Nfs4RequestContent>,
+ do_parse!(
+ a: nfs4_parse_stateid
+ >> ( Nfs4RequestContent::DelegReturn(a) )
+ )
+);
+
+named!(nfs4_req_renew<Nfs4RequestContent>,
+ do_parse!(
+ a: be_u64
+ >> ( Nfs4RequestContent::Renew(a) )
+ )
+);
+
+named!(nfs4_req_getfh<Nfs4RequestContent>,
+ do_parse!( ( Nfs4RequestContent::GetFH ) ));
+
+named!(nfs4_req_savefh<Nfs4RequestContent>,
+ do_parse!( ( Nfs4RequestContent::SaveFH ) ));
+
+named!(nfs4_req_putrootfh<Nfs4RequestContent>,
+ do_parse!( ( Nfs4RequestContent::PutRootFH ) ));
+
+named!(nfs4_req_access<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent>,
+ 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<Nfs4RequestContent<'a>>,
+}
+
+named!(pub parse_nfs4_request_compound<Nfs4RequestCompoundRecord>,
+ 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<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),
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseWrite {
+ pub count: u32,
+ pub committed: u32,
+}
+
+named!(nfs4_res_write_ok<Nfs4ResponseWrite>,
+ do_parse!(
+ count: be_u32
+ >> committed: be_u32
+ >> verifier: be_u64
+ >> (Nfs4ResponseWrite {
+ count: count,
+ committed: committed,
+ })
+));
+
+named!(nfs4_res_write<Nfs4ResponseContent>,
+ 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<Nfs4ResponseRead>,
+ 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<Nfs4ResponseContent>,
+ 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<Nfs4ResponseOpenDelegateRead<'a>>,
+}
+
+#[derive(Debug,PartialEq)]
+pub struct Nfs4ResponseOpenDelegateRead<'a> {
+ pub stateid: Nfs4StateId<'a>,
+}
+
+named!(nfs4_res_open_ok_delegate_read<Nfs4ResponseOpenDelegateRead>,
+ 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<Nfs4ResponseOpen>,
+ 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<Nfs4ResponseContent>,
+ 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<Option<Nfs4ResponseReaddirEntry<'a>>>,
+}
+
+named!(nfs4_res_readdir_entry_do<Nfs4ResponseReaddirEntry>,
+ do_parse!(
+ cookie: be_u64
+ >> name: nfs4_parse_nfsstring
+ >> attrs: nfs4_parse_attrs
+ >> ( Nfs4ResponseReaddirEntry {
+ name: name,
+ })
+));
+
+named!(nfs4_res_readdir_entry<Option<Nfs4ResponseReaddirEntry>>,
+ do_parse!(
+ value_follows: be_u32
+ >> entry: cond!(value_follows == 1, nfs4_res_readdir_entry_do)
+ >> (entry)
+));
+
+named!(nfs4_res_readdir_ok<Nfs4ResponseReaddir>,
+ 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<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> rd: cond!(status == 0, nfs4_res_readdir_ok)
+ >> ( Nfs4ResponseContent::ReadDir(status, rd) )
+));
+
+named!(nfs4_res_create_ok<Nfs4Attr>,
+ do_parse!(
+ change_info: take!(20)
+ >> attrs: nfs4_parse_attrbits
+ >> ( attrs )
+));
+
+named!(nfs4_res_create<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> attrs: cond!(status == 0, nfs4_res_create_ok)
+ >> ( Nfs4ResponseContent::Create(status) )
+));
+
+named!(nfs4_res_setattr_ok<Nfs4Attr>,
+ do_parse!(
+ attrs: nfs4_parse_attrbits
+ >> ( attrs )
+));
+
+named!(nfs4_res_setattr<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> attrs: cond!(status == 0, nfs4_res_setattr_ok)
+ >> ( Nfs4ResponseContent::SetAttr(status) )
+));
+
+named!(nfs4_res_getattr_ok<Nfs4Attr>,
+ do_parse!(
+ attrs: nfs4_parse_attrs
+ >> ( attrs )
+));
+
+named!(nfs4_res_getattr<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> attrs: cond!(status == 0, nfs4_res_getattr_ok)
+ >> ( Nfs4ResponseContent::GetAttr(status, attrs) )
+));
+
+named!(nfs4_res_openconfirm<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> stateid: cond!(status == 0, nfs4_parse_stateid)
+ >> ( Nfs4ResponseContent::OpenConfirm(status, stateid) )
+));
+
+named!(nfs4_res_close<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> stateid: cond!(status == 0, nfs4_parse_stateid)
+ >> ( Nfs4ResponseContent::Close(status, stateid) )
+));
+
+named!(nfs4_res_remove<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> cond!(status == 0, take!(20)) // change_info
+ >> ( Nfs4ResponseContent::Remove(status) )
+));
+
+named!(nfs4_res_rename<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::Rename(status) )
+));
+
+named!(nfs4_res_savefh<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::SaveFH(status) )
+));
+
+named!(nfs4_res_lookup<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::Lookup(status) )
+));
+
+named!(nfs4_res_renew<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::Renew(status) )
+));
+
+named!(nfs4_res_getfh<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> fh: cond!(status == 0, nfs4_parse_handle)
+ >> ( Nfs4ResponseContent::GetFH(status, fh) )
+));
+
+named!(nfs4_res_putfh<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::PutFH(status) )
+));
+
+named!(nfs4_res_putrootfh<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::PutRootFH(status) )
+));
+
+named!(nfs4_res_delegreturn<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ( Nfs4ResponseContent::DelegReturn(status) )
+));
+
+named!(nfs4_res_setclientid<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> client_id: be_u64
+ >> verifier: be_u32
+ >> ( Nfs4ResponseContent::SetClientId(status) )
+));
+
+named!(nfs4_res_setclientid_confirm<Nfs4ResponseContent>,
+ 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<Nfs4ResponseAccess>,
+ do_parse!(
+ s: be_u32
+ >> a: be_u32
+ >> (Nfs4ResponseAccess {
+ supported_types: s,
+ access_rights: a,
+ })
+));
+
+named!(nfs4_res_access<Nfs4ResponseContent>,
+ do_parse!(
+ status: be_u32
+ >> ad: cond!(status == 0, nfs4_res_access_ok)
+ >> ( Nfs4ResponseContent::Access(
+ status, ad, ))
+));
+
+named!(nfs4_res_compound_command<Nfs4ResponseContent>,
+ 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<Nfs4ResponseContent<'a>>,
+}
+
+named!(pub parse_nfs4_response_compound<Nfs4ResponseCompoundRecord>,
+ 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,
+ })
+));
},
}.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()
+}