use std;
use std::cmp;
use std::collections::HashMap;
use std::ffi::CString;
use nom7::{Err, Needed};
use suricata_sys::sys::{
AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
SCAppLayerProtoDetectConfProtoDetectionEnabled, SCAppLayerProtoDetectPPParseConfPorts,
SCAppLayerProtoDetectPPRegister,
};
use crate::applayer;
use crate::applayer::*;
use crate::conf::*;
use crate::core::*;
use crate::direction::Direction;
use crate::direction::DIR_BOTH;
use crate::filecontainer::*;
use crate::filetracker::*;
use crate::flow::{Flow, flow_get_last_time};
use crate::frames::*;
use crate::nfs::nfs2_records::*;
use crate::nfs::nfs3_records::*;
use crate::nfs::nfs_records::*;
use crate::nfs::rpc_records::*;
use crate::nfs::types::*;
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;
#[derive(AppLayerFrameType)]
pub enum NFSFrameType {
RPCPdu,
RPCHdr,
RPCData,
RPCCreds,
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 {
pub direction: Direction,
pub file_additional_procs: Vec<u32>,
pub chunk_count: u32,
pub file_last_xid: u32,
pub post_gap_ts: u64,
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) };
}
}
unsafe extern "C" fn nfs_gettxfiles(
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,
pub xid: u32,
pub procedure: u32,
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,
pub request_done: bool,
pub response_done: bool,
pub nfs_version: u16,
pub is_file_tx: bool,
pub is_file_closed: bool,
pub file_handle: Vec<u8>,
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>,
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,
}
}
}
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,
pub requestmap: HashMap<u32, NFSRequestXidMap>,
pub namemap: HashMap<Vec<u8>, Vec<u8>>,
pub transactions: Vec<NFSTransaction>,
pub ts_chunk_xid: u32,
pub tc_chunk_xid: u32,
pub ts_chunk_left: u32,
pub tc_chunk_left: u32,
ts_chunk_fh: Vec<u8>,
ts_ssn_gap: bool,
tc_ssn_gap: bool,
ts_gap: bool, tc_gap: bool,
is_udp: bool,
check_post_gap_file_txs: bool,
post_gap_files_checked: bool,
pub nfs_version: u16,
tx_id: u64,
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 {
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 } {
for tx_old in &mut self.transactions {
if !tx_old.request_done || !tx_old.response_done {
tx_old.tx_data.updated_tc = true;
tx_old.tx_data.updated_ts = true;
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) {
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> {
return self.transactions.iter().find(|&tx| tx.id == tx_id + 1);
}
pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFSTransaction> {
return self
.transactions
.iter_mut()
.find(|tx| !tx.is_file_tx && tx.xid == tx_xid);
}
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);
}
pub fn mark_response_tx_done(
&mut self, flow: *mut Flow, xid: u32, rpc_status: u32, nfs_status: u32, resp_handle: &[u8],
) {
if let Some(mytx) = self.get_tx_by_xid(xid) {
mytx.tx_data.updated_tc = true;
mytx.tx_data.updated_ts = true;
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
);
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToClient as i32);
} else {
}
}
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
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,
None,
);
let _rpc_udp_tc_data = Frame::new(
flow,
stream_slice,
&input[8..],
rpc_len - 8,
NFSFrameType::RPCData as u8,
None,
);
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,
None,
);
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,
None,
);
let _rpc_tcp_tc_data = Frame::new(
flow,
stream_slice,
&input[12..],
rpc_tcp_len - 12,
NFSFrameType::RPCData as u8,
None,
);
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,
None,
);
SCLogDebug!("nfs_tc_pdu frame {:?}", _nfs_tc_pdu);
let _nfs_res_status = Frame::new(
flow,
stream_slice,
input,
4,
NFSFrameType::NFSStatus as u8,
None,
);
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,
None,
);
SCLogDebug!("nfs4_tc_pdu frame {:?}", _nfs4_tc_pdu);
let _nfs4_tc_status = Frame::new(
flow,
stream_slice,
input,
4,
NFSFrameType::NFS4Status as u8,
None,
);
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,
None,
);
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,
None,
);
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;
}
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 {
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 {
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) {
if let Some(n) = self.namemap.get(&xidmap.file_handle) {
SCLogDebug!("xidmap_handle2name: name {:?}", n);
xidmap.file_name = n.to_vec();
} else {
SCLogDebug!(
"xidmap_handle2name: object {:?} not found",
xidmap.file_handle
);
}
}
fn process_request_record(
&mut self, flow: *mut 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(flow, 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
}; 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);
tx.tx_data.updated_tc = true;
tx.tx_data.updated_ts = true;
return Some(tx);
}
}
}
SCLogDebug!("Failed to find NFS TX with handle {:?}", file_handle);
return None;
}
pub fn process_write_record<'b>(&mut self, flow: *mut Flow, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 {
let mut fill_bytes = 0;
let pad = w.count % 4;
if pad != 0 {
fill_bytes = 4 - pad;
}
if w.count == 0 || w.count > 16777216 {
return 0;
}
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;
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToClient as i32);
}
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;
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToServer as i32);
}
}
}
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, flow: *mut Flow, 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(flow, r, w);
}
fn process_reply_record(
&mut self, flow: *mut 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
);
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(flow, 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(flow, 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(flow, r, &mut xidmap);
return 0;
}
_ => {
SCLogDebug!("Invalid NFS version");
self.set_event(NFSEvent::NonExistingVersion);
return 0;
}
}
}
fn filetracker_update(&mut self, flow: *mut Flow, 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;
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;
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 {
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;
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 {
SCLogDebug!(
"QUEUED size {} while we've seen GAPs. Truncating file.",
queued_data
);
filetracker_trunc(&mut tdf.file_tracker);
}
}
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);
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
);
if !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToClient as i32);
}
} else {
tx.request_done = true;
tx.is_file_closed = true;
SCLogDebug!(
"TX {} request is done now that the file track is ready",
tx.id
);
if !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToServer as i32);
}
}
}
cs
} else {
0
}
}
None => 0,
};
return consumed;
}
pub fn process_read_record<'b>(
&mut self, flow: *mut Flow, 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;
}
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;
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToServer as i32);
if !is_partial {
tx.response_done = true;
SCLogDebug!("TX {} is DONE", tx.id);
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToClient as i32);
}
}
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;
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToServer as i32);
if !is_partial {
tx.response_done = true;
SCLogDebug!("TX {} is DONE", tx.id);
sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToClient as i32);
}
}
}
}
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, flow: *mut Flow, 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(flow, 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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();
}
fn parse_tcp_partial_data_ts<'b>(
&mut self, flow: *mut Flow, base_input: &'b [u8], cur_i: &'b [u8], phdr: &RpcRequestPacketPartial,
rec_size: usize,
) -> AppLayerResult {
if rec_size >= 512 && cur_i.len() >= 44 {
SCLogDebug!("large record {}, likely file xfer", rec_size);
if phdr.procedure == NFSPROC3_WRITE {
SCLogDebug!(
"CONFIRMED WRITE: large record {}, file chunk xfer",
rec_size
);
match parse_rpc(cur_i, false) {
Ok((_rem, ref hdr)) => {
debug_validate_bug_on!(!_rem.is_empty());
match parse_nfs3_request_write(hdr.prog_data, false) {
Ok((_, ref w)) => {
self.process_partial_write_request_record(flow, 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(_)) => {
}
}
}
Err(Err::Incomplete(_)) => {
SCLogDebug!("TS data incomplete");
}
Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
self.set_event(NFSEvent::MalformedData);
SCLogDebug!("Parsing failed: {:?}", _e);
return AppLayerResult::err();
}
}
}
}
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);
}
pub fn parse_tcp_data_ts(
&mut self, flow: *mut Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let mut cur_i = stream_slice.as_slice();
let consumed = self.filetracker_update(flow, 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() {
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;
if rec_size > cur_i.len() {
return self.parse_tcp_partial_data_ts(
flow,
stream_slice.as_slice(),
cur_i,
rpc_phdr,
rec_size,
);
}
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);
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();
}
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()
}
fn parse_tcp_partial_data_tc<'b>(
&mut self, flow: *mut Flow, base_input: &'b [u8], cur_i: &'b [u8], phdr: &RpcPacketHeader, rec_size: usize,
) -> AppLayerResult {
if rec_size >= 512 && cur_i.len() >= 128 {
SCLogDebug!("large record {}, likely file xfer", rec_size);
if self.peek_reply_record(phdr) == NFSPROC3_READ {
SCLogDebug!(
"CONFIRMED large READ record {}, likely file chunk xfer",
rec_size
);
match parse_rpc_reply(cur_i, false) {
Ok((_rem, ref hdr)) => {
debug_validate_bug_on!(!_rem.is_empty());
match parse_nfs3_reply_read(hdr.prog_data, false) {
Ok((_, ref r)) => {
self.process_partial_read_reply_record(flow, 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(_)) => {
}
}
}
Err(Err::Incomplete(_)) => {
SCLogDebug!("TC data incomplete");
}
Err(Err::Error(_e)) | Err(Err::Failure(_e)) => {
self.set_event(NFSEvent::MalformedData);
SCLogDebug!("Parsing failed: {:?}", _e);
return AppLayerResult::err();
}
}
}
}
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);
}
pub fn parse_tcp_data_tc(
&mut self, flow: *mut Flow, stream_slice: &StreamSlice,
) -> AppLayerResult {
let mut cur_i = stream_slice.as_slice();
let consumed = self.filetracker_update(flow, 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;
if rec_size > cur_i.len() {
return self.parse_tcp_partial_data_tc(
flow,
stream_slice.as_slice(),
cur_i,
rpc_phdr,
rec_size,
);
}
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(_)) => {
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);
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();
}
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()
}
pub fn parse_udp_ts(
&mut self, flow: *mut 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()
}
pub fn parse_udp_tc(
&mut self, flow: *mut 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()
}
}
extern "C" fn 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 _;
}
extern "C" fn nfs_state_free(state: *mut std::os::raw::c_void) {
SCLogDebug!("freeing state");
std::mem::drop(unsafe { Box::from_raw(state as *mut NFSState) });
}
unsafe extern "C" fn nfs_parse_request(
flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
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 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(flow).as_secs());
state.parse_tcp_data_ts(flow, &stream_slice)
}
extern "C" fn nfs_parse_request_tcp_gap(state: &mut NFSState, input_len: u32) -> AppLayerResult {
state.parse_tcp_data_ts_gap(input_len)
}
unsafe extern "C" fn nfs_parse_response(
flow: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
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 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(flow).as_secs());
state.parse_tcp_data_tc(flow, &stream_slice)
}
extern "C" fn nfs_parse_response_tcp_gap(state: &mut NFSState, input_len: u32) -> AppLayerResult {
state.parse_tcp_data_tc_gap(input_len)
}
unsafe extern "C" fn nfs_parse_request_udp(
f: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
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)
}
unsafe extern "C" fn nfs_parse_response_udp(
f: *mut Flow, state: *mut std::os::raw::c_void, _pstate: *mut AppLayerParserState,
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)
}
pub unsafe extern "C" fn nfs_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
let state = cast_pointer!(state, NFSState);
SCLogDebug!("nfs_state_get_tx_count: returning {}", state.tx_id);
return state.tx_id;
}
unsafe extern "C" fn 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();
}
}
}
unsafe extern "C" fn 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);
}
unsafe extern "C" fn 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 == u8::from(Direction::ToServer) && tx.request_done {
SCLogDebug!("TOSERVER progress 1");
return 1;
} else if direction == u8::from(Direction::ToClient) && tx.response_done {
SCLogDebug!("TOCLIENT progress 1");
return 1;
} else {
SCLogDebug!("{} progress 0", direction);
return 0;
}
}
unsafe extern "C" fn 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!(nfs_get_state_data, NFSState);
#[no_mangle]
pub unsafe extern "C" fn SCNfsTxGetProcedures(
tx: &mut NFSTransaction, i: u16, procedure: *mut u32,
) -> u8 {
if i == 0 {
*procedure = tx.procedure;
return 1;
}
if !tx.is_file_tx {
return 0;
}
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 SCNfsTxGetVersion(tx: &mut NFSTransaction, version: *mut u32) {
*version = tx.nfs_version as u32;
}
#[no_mangle]
pub unsafe extern "C" fn SCNfsInit(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;
}
}
}
}
unsafe extern "C" fn nfs_probe_ms(
_flow: *const Flow, direction: u8, input: *const u8, len: u32, rdir: *mut u8,
) -> AppProto {
if input.is_null() {
return ALPROTO_UNKNOWN;
}
let slice: &[u8] = build_slice!(input, len as usize);
SCLogDebug!("nfs_probe_ms: probing direction {:02x}", direction);
let mut adirection: u8 = 0;
match nfs_probe_dir(slice, &mut adirection) {
1 => {
if adirection == u8::from(Direction::ToServer) {
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,
}
}
unsafe extern "C" fn ffi_nfs_probe(
_f: *const Flow, direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
) -> AppProto {
if input.is_null() {
return ALPROTO_UNKNOWN;
}
let slice: &[u8] = build_slice!(input, len as usize);
SCLogDebug!("ffi_nfs_probe: running probe");
match nfs_probe(slice, direction.into()) {
1 => ALPROTO_NFS,
-1 => ALPROTO_FAILED,
_ => ALPROTO_UNKNOWN,
}
}
unsafe extern "C" fn nfs_probe_udp_ts(
_f: *const Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
) -> AppProto {
if input.is_null() {
return ALPROTO_UNKNOWN;
}
let slice: &[u8] = build_slice!(input, len as usize);
match nfs_probe_udp(slice, Direction::ToServer) {
1 => ALPROTO_NFS,
-1 => ALPROTO_FAILED,
_ => ALPROTO_UNKNOWN,
}
}
unsafe extern "C" fn nfs_probe_udp_tc(
_f: *const Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
) -> AppProto {
if input.is_null() {
return ALPROTO_UNKNOWN;
}
let slice: &[u8] = build_slice!(input, len as usize);
match nfs_probe_udp(slice, Direction::ToClient) {
1 => ALPROTO_NFS,
-1 => ALPROTO_FAILED,
_ => ALPROTO_UNKNOWN,
}
}
const PARSER_NAME: &[u8] = b"nfs\0";
#[no_mangle]
pub unsafe extern "C" fn SCRegisterNfsParser() {
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: nfs_state_new,
state_free: nfs_state_free,
tx_free: nfs_state_tx_free,
parse_ts: nfs_parse_request,
parse_tc: nfs_parse_response,
get_tx_count: nfs_state_get_tx_count,
get_tx: nfs_state_get_tx,
tx_comp_st_ts: 1,
tx_comp_st_tc: 1,
tx_get_progress: 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(nfs_gettxfiles),
get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>),
get_tx_data: nfs_get_tx_data,
get_state_data: nfs_get_state_data,
apply_tx_config: None,
flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS,
get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name),
get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id),
get_state_id_by_name: None,
get_state_name_by_id: None,
};
let ip_proto_str = CString::new("tcp").unwrap();
if SCAppLayerProtoDetectConfProtoDetectionEnabled(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 SCAppLayerProtoDetectPPParseConfPorts(
ip_proto_str.as_ptr(),
IPPROTO_TCP,
parser.name,
ALPROTO_NFS,
0,
NFS_MIN_FRAME_LEN,
Some(nfs_probe_ms),
Some(nfs_probe_ms),
) == 0
{
SCLogDebug!("No NFSTCP app-layer configuration, enabling NFSTCP detection TCP detection on port {:?}.",
default_port);
SCAppLayerProtoDetectPPRegister(
IPPROTO_TCP,
default_port.as_ptr(),
ALPROTO_NFS,
0,
NFS_MIN_FRAME_LEN,
Direction::ToServer.into(),
Some(nfs_probe_ms),
Some(nfs_probe_ms),
);
}
} else {
SCAppLayerProtoDetectPPRegister(
IPPROTO_TCP,
default_port.as_ptr(),
ALPROTO_NFS,
0,
NFS_MIN_FRAME_LEN,
Direction::ToServer.into(),
Some(ffi_nfs_probe),
Some(ffi_nfs_probe),
);
}
if SCAppLayerParserConfParserEnabled(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 SCRegisterNfsUdpParser() {
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: nfs_state_new,
state_free: nfs_state_free,
tx_free: nfs_state_tx_free,
parse_ts: nfs_parse_request_udp,
parse_tc: nfs_parse_response_udp,
get_tx_count: nfs_state_get_tx_count,
get_tx: nfs_state_get_tx,
tx_comp_st_ts: 1,
tx_comp_st_tc: 1,
tx_get_progress: 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(nfs_gettxfiles),
get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>),
get_tx_data: nfs_get_tx_data,
get_state_data: nfs_get_state_data,
apply_tx_config: None,
flags: 0,
get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name),
get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id),
get_state_id_by_name: None,
get_state_name_by_id: None,
};
let ip_proto_str = CString::new("udp").unwrap();
if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
ALPROTO_NFS = alproto;
if SCAppLayerProtoDetectPPParseConfPorts(
ip_proto_str.as_ptr(),
IPPROTO_UDP,
parser.name,
ALPROTO_NFS,
0,
NFS_MIN_FRAME_LEN,
Some(nfs_probe_udp_ts),
Some(nfs_probe_udp_tc),
) == 0
{
SCLogDebug!("No NFSUDP app-layer configuration, enabling NFSUDP detection UDP detection on port {:?}.",
default_port);
SCAppLayerProtoDetectPPRegister(
IPPROTO_UDP,
default_port.as_ptr(),
ALPROTO_NFS,
0,
NFS_MIN_FRAME_LEN,
Direction::ToServer.into(),
Some(nfs_probe_udp_ts),
Some(nfs_probe_udp_tc),
);
}
if SCAppLayerParserConfParserEnabled(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.");
}
}