diff options
Diffstat (limited to 'rust/src')
182 files changed, 69728 insertions, 0 deletions
diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs new file mode 100644 index 0000000..97db321 --- /dev/null +++ b/rust/src/applayer.rs @@ -0,0 +1,703 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Parser registration functions and common interface module. + +use std; +use crate::core::{self,DetectEngineState,Flow,AppLayerEventType,AppProto,Direction}; +use crate::filecontainer::FileContainer; +use std::os::raw::{c_void,c_char,c_int}; +use crate::core::SC; +use std::ffi::CStr; +use crate::core::StreamingBufferConfig; + +// Make the AppLayerEvent derive macro available to users importing +// AppLayerEvent from this module. +pub use suricata_derive::AppLayerEvent; + +#[repr(C)] +pub struct StreamSlice { + input: *const u8, + input_len: u32, + /// STREAM_* flags + flags: u8, + offset: u64, +} + +impl StreamSlice { + + /// Create a StreamSlice from a Rust slice. Useful in unit tests. + #[cfg(test)] + pub fn from_slice(slice: &[u8], flags: u8, offset: u64) -> Self { + Self { + input: slice.as_ptr(), + input_len: slice.len() as u32, + flags, + offset + } + } + + pub fn is_gap(&self) -> bool { + self.input.is_null() && self.input_len > 0 + } + pub fn gap_size(&self) -> u32 { + self.input_len + } + pub fn as_slice(&self) -> &[u8] { + unsafe { std::slice::from_raw_parts(self.input, self.input_len as usize) } + } + pub fn is_empty(&self) -> bool { + self.input_len == 0 + } + pub fn len(&self) -> u32 { + self.input_len + } + pub fn offset_from(&self, slice: &[u8]) -> u32 { + self.len() - slice.len() as u32 + } + pub fn flags(&self) -> u8 { + self.flags + } +} + +#[repr(C)] +#[derive(Default, Debug,PartialEq, Eq)] +pub struct AppLayerTxConfig { + /// config: log flags + log_flags: u8, +} + +impl AppLayerTxConfig { + pub fn new() -> Self { + Self { + log_flags: 0, + } + } +} + +#[repr(C)] +#[derive(Debug, PartialEq, Eq)] +pub struct AppLayerTxData { + /// config: log flags + pub config: AppLayerTxConfig, + + /// logger flags for tx logging api + logged: LoggerFlags, + + /// track file open/logs so we can know how long to keep the tx + pub files_opened: u32, + pub files_logged: u32, + pub files_stored: u32, + + pub file_flags: u16, + + /// Indicated if a file tracking tx, and if so in which direction: + /// 0: not a file tx + /// STREAM_TOSERVER: file tx, files only in toserver dir + /// STREAM_TOCLIENT: file tx , files only in toclient dir + /// STREAM_TOSERVER|STREAM_TOCLIENT: files possible in both dirs + pub file_tx: u8, + + /// detection engine flags for use by detection engine + detect_flags_ts: u64, + detect_flags_tc: u64, + + de_state: *mut DetectEngineState, + pub events: *mut core::AppLayerDecoderEvents, +} + +impl Default for AppLayerTxData { + fn default() -> Self { + Self::new() + } +} + +impl Drop for AppLayerTxData { + fn drop(&mut self) { + if !self.de_state.is_null() { + core::sc_detect_engine_state_free(self.de_state); + } + if !self.events.is_null() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + } +} + +impl AppLayerTxData { + /// Create new AppLayerTxData for a transaction that covers both + /// directions. + pub fn new() -> Self { + Self { + config: AppLayerTxConfig::new(), + logged: LoggerFlags::new(), + files_opened: 0, + files_logged: 0, + files_stored: 0, + file_flags: 0, + file_tx: 0, + detect_flags_ts: 0, + detect_flags_tc: 0, + de_state: std::ptr::null_mut(), + events: std::ptr::null_mut(), + } + } + + /// Create new AppLayerTxData for a transaction in a single + /// direction. + pub fn for_direction(direction: Direction) -> Self { + let (detect_flags_ts, detect_flags_tc) = match direction { + Direction::ToServer => (0, APP_LAYER_TX_SKIP_INSPECT_FLAG), + Direction::ToClient => (APP_LAYER_TX_SKIP_INSPECT_FLAG, 0), + }; + Self { + config: AppLayerTxConfig::new(), + logged: LoggerFlags::new(), + files_opened: 0, + files_logged: 0, + files_stored: 0, + file_flags: 0, + file_tx: 0, + detect_flags_ts, + detect_flags_tc, + de_state: std::ptr::null_mut(), + events: std::ptr::null_mut(), + } + } + + pub fn init_files_opened(&mut self) { + self.files_opened = 1; + } + + pub fn incr_files_opened(&mut self) { + self.files_opened += 1; + } + + pub fn set_event(&mut self, event: u8) { + core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, event); + } + + pub fn update_file_flags(&mut self, state_flags: u16) { + if (self.file_flags & state_flags) != state_flags { + SCLogDebug!("updating tx file_flags {:04x} with state flags {:04x}", self.file_flags, state_flags); + self.file_flags |= state_flags; + } + } +} + +#[macro_export] +macro_rules!export_tx_data_get { + ($name:ident, $type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $name(tx: *mut std::os::raw::c_void) + -> *mut $crate::applayer::AppLayerTxData + { + let tx = &mut *(tx as *mut $type); + &mut tx.tx_data + } + } +} + +#[repr(C)] +#[derive(Default,Debug,PartialEq, Eq,Copy,Clone)] +pub struct AppLayerStateData { + pub file_flags: u16, +} + +impl AppLayerStateData { + pub fn new() -> Self { + Self { + file_flags: 0, + } + } +} + +#[macro_export] +macro_rules!export_state_data_get { + ($name:ident, $type:ty) => { + #[no_mangle] + pub unsafe extern "C" fn $name(state: *mut std::os::raw::c_void) + -> *mut $crate::applayer::AppLayerStateData + { + let state = &mut *(state as *mut $type); + &mut state.state_data + } + } +} + +#[repr(C)] +#[derive(Default,Debug,PartialEq, Eq,Copy,Clone)] +pub struct AppLayerResult { + pub status: i32, + pub consumed: u32, + pub needed: u32, +} + +impl AppLayerResult { + /// parser has successfully processed in the input, and has consumed all of it + pub fn ok() -> Self { + Default::default() + } + /// parser has hit an unrecoverable error. Returning this to the API + /// leads to no further calls to the parser. + pub fn err() -> Self { + return Self { + status: -1, + ..Default::default() + }; + } + /// parser needs more data. Through 'consumed' it will indicate how many + /// of the input bytes it has consumed. Through 'needed' it will indicate + /// how many more bytes it needs before getting called again. + /// Note: consumed should never be more than the input len + /// needed + consumed should be more than the input len + pub fn incomplete(consumed: u32, needed: u32) -> Self { + return Self { + status: 1, + consumed, + needed, + }; + } + + pub fn is_incomplete(self) -> bool { + self.status == 1 + } +} + +impl From<bool> for AppLayerResult { + fn from(v: bool) -> Self { + if !v { + Self::err() + } else { + Self::ok() + } + } +} + +impl From<i32> for AppLayerResult { + fn from(v: i32) -> Self { + if v < 0 { + Self::err() + } else { + Self::ok() + } + } +} + +/// Rust parser declaration +#[repr(C)] +pub struct RustParser { + /// Parser name. + pub name: *const c_char, + /// Default port + pub default_port: *const c_char, + + /// IP Protocol (core::IPPROTO_UDP, core::IPPROTO_TCP, etc.) + pub ipproto: u8, + + /// Probing function, for packets going to server + pub probe_ts: Option<ProbeFn>, + /// Probing function, for packets going to client + pub probe_tc: Option<ProbeFn>, + + /// Minimum frame depth for probing + pub min_depth: u16, + /// Maximum frame depth for probing + pub max_depth: u16, + + /// Allocation function for a new state + pub state_new: StateAllocFn, + /// Function called to free a state + pub state_free: StateFreeFn, + + /// Parsing function, for packets going to server + pub parse_ts: ParseFn, + /// Parsing function, for packets going to client + pub parse_tc: ParseFn, + + /// Get the current transaction count + pub get_tx_count: StateGetTxCntFn, + /// Get a transaction + pub get_tx: StateGetTxFn, + /// Function called to free a transaction + pub tx_free: StateTxFreeFn, + /// Progress values at which the tx is considered complete in a direction + pub tx_comp_st_ts: c_int, + pub tx_comp_st_tc: c_int, + /// Function returning the current transaction progress + pub tx_get_progress: StateGetProgressFn, + + /// Function to get an event id from a description + pub get_eventinfo: Option<GetEventInfoFn>, + /// Function to get an event description from an event id + pub get_eventinfo_byid: Option<GetEventInfoByIdFn>, + + /// Function to allocate local storage + pub localstorage_new: Option<LocalStorageNewFn>, + /// Function to free local storage + pub localstorage_free: Option<LocalStorageFreeFn>, + + /// Function to get files + pub get_tx_files: Option<GetTxFilesFn>, + + /// Function to get the TX iterator + pub get_tx_iterator: Option<GetTxIteratorFn>, + + pub get_state_data: GetStateDataFn, + pub get_tx_data: GetTxDataFn, + + // Function to apply config to a TX. Optional. Normal (bidirectional) + // transactions don't need to set this. It is meant for cases where + // the requests and responses are not sharing tx. It is then up to + // the implementation to make sure the config is applied correctly. + pub apply_tx_config: Option<ApplyTxConfigFn>, + + pub flags: u32, + + /// Function to handle the end of data coming on one of the sides + /// due to the stream reaching its 'depth' limit. + pub truncate: Option<TruncateFn>, + + pub get_frame_id_by_name: Option<GetFrameIdByName>, + pub get_frame_name_by_id: Option<GetFrameNameById>, +} + +/// Create a slice, given a buffer and a length +/// +/// UNSAFE ! +#[macro_export] +macro_rules! build_slice { + ($buf:ident, $len:expr) => ( std::slice::from_raw_parts($buf, $len) ); +} + +/// Cast pointer to a variable, as a mutable reference to an object +/// +/// UNSAFE ! +#[macro_export] +macro_rules! cast_pointer { + ($ptr:ident, $ty:ty) => ( &mut *($ptr as *mut $ty) ); +} + +/// helper for the GetTxFilesFn. Not meant to be embedded as the config +/// pointer is passed around in the API. +#[allow(non_snake_case)] +#[repr(C)] +pub struct AppLayerGetFileState { + pub fc: *mut FileContainer, + pub cfg: *const StreamingBufferConfig, +} +impl AppLayerGetFileState { + pub fn err() -> AppLayerGetFileState { + AppLayerGetFileState { fc: std::ptr::null_mut(), cfg: std::ptr::null() } + } +} + +pub type ParseFn = unsafe extern "C" fn (flow: *const Flow, + state: *mut c_void, + pstate: *mut c_void, + stream_slice: StreamSlice, + data: *const c_void) -> AppLayerResult; +pub type ProbeFn = unsafe extern "C" fn (flow: *const Flow, flags: u8, input:*const u8, input_len: u32, rdir: *mut u8) -> AppProto; +pub type StateAllocFn = extern "C" fn (*mut c_void, AppProto) -> *mut c_void; +pub type StateFreeFn = unsafe extern "C" fn (*mut c_void); +pub type StateTxFreeFn = unsafe extern "C" fn (*mut c_void, u64); +pub type StateGetTxFn = unsafe extern "C" fn (*mut c_void, u64) -> *mut c_void; +pub type StateGetTxCntFn = unsafe extern "C" fn (*mut c_void) -> u64; +pub type StateGetProgressFn = unsafe extern "C" fn (*mut c_void, u8) -> c_int; +pub type GetEventInfoFn = unsafe extern "C" fn (*const c_char, *mut c_int, *mut AppLayerEventType) -> c_int; +pub type GetEventInfoByIdFn = unsafe extern "C" fn (c_int, *mut *const c_char, *mut AppLayerEventType) -> i8; +pub type LocalStorageNewFn = extern "C" fn () -> *mut c_void; +pub type LocalStorageFreeFn = extern "C" fn (*mut c_void); +pub type GetTxFilesFn = unsafe extern "C" fn (*mut c_void, *mut c_void, u8) -> AppLayerGetFileState; +pub type GetTxIteratorFn = unsafe extern "C" fn (ipproto: u8, alproto: AppProto, + state: *mut c_void, + min_tx_id: u64, + max_tx_id: u64, + istate: &mut u64) + -> AppLayerGetTxIterTuple; +pub type GetTxDataFn = unsafe extern "C" fn(*mut c_void) -> *mut AppLayerTxData; +pub type GetStateDataFn = unsafe extern "C" fn(*mut c_void) -> *mut AppLayerStateData; +pub type ApplyTxConfigFn = unsafe extern "C" fn (*mut c_void, *mut c_void, c_int, AppLayerTxConfig); +pub type TruncateFn = unsafe extern "C" fn (*mut c_void, u8); +pub type GetFrameIdByName = unsafe extern "C" fn(*const c_char) -> c_int; +pub type GetFrameNameById = unsafe extern "C" fn(u8) -> *const c_char; + + +// Defined in app-layer-register.h +extern { + pub fn AppLayerRegisterProtocolDetection(parser: *const RustParser, enable_default: c_int) -> AppProto; + pub fn AppLayerRegisterParserAlias(parser_name: *const c_char, alias_name: *const c_char); +} + +#[allow(non_snake_case)] +pub unsafe fn AppLayerRegisterParser(parser: *const RustParser, alproto: AppProto) -> c_int { + (SC.unwrap().AppLayerRegisterParser)(parser, alproto) +} + +// Defined in app-layer-detect-proto.h +extern { + pub fn AppLayerProtoDetectPPRegister(ipproto: u8, portstr: *const c_char, alproto: AppProto, + min_depth: u16, max_depth: u16, dir: u8, + pparser1: ProbeFn, pparser2: ProbeFn); + pub fn AppLayerProtoDetectPPParseConfPorts(ipproto_name: *const c_char, ipproto: u8, + alproto_name: *const c_char, alproto: AppProto, + min_depth: u16, max_depth: u16, + pparser_ts: ProbeFn, pparser_tc: ProbeFn) -> i32; + pub fn AppLayerProtoDetectPMRegisterPatternCS(ipproto: u8, alproto: AppProto, + pattern: *const c_char, depth: u16, + offset: u16, direction: u8) -> c_int; + pub fn AppLayerProtoDetectPMRegisterPatternCSwPP(ipproto: u8, alproto: AppProto, + pattern: *const c_char, depth: u16, + offset: u16, direction: u8, ppfn: ProbeFn, + pp_min_depth: u16, pp_max_depth: u16) -> c_int; + pub fn AppLayerProtoDetectConfProtoDetectionEnabled(ipproto: *const c_char, proto: *const c_char) -> c_int; + pub fn AppLayerProtoDetectConfProtoDetectionEnabledDefault(ipproto: *const c_char, proto: *const c_char, default: bool) -> c_int; + pub fn AppLayerRequestProtocolTLSUpgrade(flow: *const Flow) -> bool; +} + +// Defined in app-layer-parser.h +pub const APP_LAYER_PARSER_NO_INSPECTION : u16 = BIT_U16!(1); +pub const APP_LAYER_PARSER_NO_REASSEMBLY : u16 = BIT_U16!(2); +pub const APP_LAYER_PARSER_NO_INSPECTION_PAYLOAD : u16 = BIT_U16!(3); +pub const APP_LAYER_PARSER_BYPASS_READY : u16 = BIT_U16!(4); +pub const APP_LAYER_PARSER_EOF_TS : u16 = BIT_U16!(5); +pub const APP_LAYER_PARSER_EOF_TC : u16 = BIT_U16!(6); +pub const APP_LAYER_PARSER_TRUNC_TS : u16 = BIT_U16!(7); +pub const APP_LAYER_PARSER_TRUNC_TC : u16 = BIT_U16!(8); + +pub const APP_LAYER_PARSER_OPT_ACCEPT_GAPS: u32 = BIT_U32!(0); + +pub const APP_LAYER_TX_SKIP_INSPECT_FLAG: u64 = BIT_U64!(62); + +extern { + pub fn AppLayerParserStateSetFlag(state: *mut c_void, flag: u16); + pub fn AppLayerParserStateIssetFlag(state: *mut c_void, flag: u16) -> u16; + pub fn AppLayerParserSetStreamDepth(ipproto: u8, alproto: AppProto, stream_depth: u32); + pub fn AppLayerParserConfParserEnabled(ipproto: *const c_char, proto: *const c_char) -> c_int; +} + +#[repr(C)] +pub struct AppLayerGetTxIterTuple { + tx_ptr: *mut std::os::raw::c_void, + tx_id: u64, + has_next: bool, +} + +impl AppLayerGetTxIterTuple { + pub fn with_values(tx_ptr: *mut std::os::raw::c_void, tx_id: u64, has_next: bool) -> AppLayerGetTxIterTuple { + AppLayerGetTxIterTuple { + tx_ptr, tx_id, has_next, + } + } + pub fn not_found() -> AppLayerGetTxIterTuple { + AppLayerGetTxIterTuple { + tx_ptr: std::ptr::null_mut(), tx_id: 0, has_next: false, + } + } +} + +/// LoggerFlags tracks which loggers have already been executed. +#[repr(C)] +#[derive(Default, Debug,PartialEq, Eq)] +pub struct LoggerFlags { + flags: u32, +} + +impl LoggerFlags { + + pub fn new() -> Self { + Default::default() + } + + pub fn get(&self) -> u32 { + self.flags + } + + pub fn set(&mut self, bits: u32) { + self.flags = bits; + } + +} + +/// AppLayerEvent trait that will be implemented on enums that +/// derive AppLayerEvent. +pub trait AppLayerEvent { + /// Return the enum variant of the given ID. + fn from_id(id: i32) -> Option<Self> where Self: std::marker::Sized; + + /// Convert the enum variant to a C-style string (suffixed with \0). + fn to_cstring(&self) -> &str; + + /// Return the enum variant for the given name. + fn from_string(s: &str) -> Option<Self> where Self: std::marker::Sized; + + /// Return the ID value of the enum variant. + fn as_i32(&self) -> i32; + + unsafe extern "C" fn get_event_info( + event_name: *const std::os::raw::c_char, + event_id: *mut std::os::raw::c_int, + event_type: *mut core::AppLayerEventType, + ) -> std::os::raw::c_int; + + unsafe extern "C" fn get_event_info_by_id( + event_id: std::os::raw::c_int, + event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, + ) -> i8; +} + +/// Generic `get_info_info` implementation for enums implementing +/// AppLayerEvent. +/// +/// Normally usage of this function will be generated by +/// derive(AppLayerEvent), for example: +/// +/// ```rust,ignore +/// #[derive(AppLayerEvent)] +/// enum AppEvent { +/// EventOne, +/// EventTwo, +/// } +/// +/// get_event_info::<AppEvent>(...) +/// ``` +#[inline(always)] +pub unsafe fn get_event_info<T: AppLayerEvent>( + event_name: *const std::os::raw::c_char, + event_id: *mut std::os::raw::c_int, + event_type: *mut core::AppLayerEventType, +) -> std::os::raw::c_int { + if event_name.is_null() { + return -1; + } + + let event = match CStr::from_ptr(event_name).to_str().map(T::from_string) { + Ok(Some(event)) => event.as_i32(), + _ => -1, + }; + *event_type = core::AppLayerEventType::APP_LAYER_EVENT_TYPE_TRANSACTION; + *event_id = event as std::os::raw::c_int; + return 0; +} + +/// Generic `get_info_info_by_id` implementation for enums implementing +/// AppLayerEvent. +#[inline(always)] +pub unsafe fn get_event_info_by_id<T: AppLayerEvent>( + event_id: std::os::raw::c_int, + event_name: *mut *const std::os::raw::c_char, + event_type: *mut core::AppLayerEventType, +) -> i8 { + if let Some(e) = T::from_id(event_id) { + *event_name = e.to_cstring().as_ptr() as *const std::os::raw::c_char; + *event_type = core::AppLayerEventType::APP_LAYER_EVENT_TYPE_TRANSACTION; + return 0; + } + return -1; +} + +/// Transaction trait. +/// +/// This trait defines methods that a Transaction struct must implement +/// in order to define some generic helper functions. +pub trait Transaction { + fn id(&self) -> u64; +} + +pub trait State<Tx: Transaction> { + /// Return the number of transactions in the state's transaction collection. + fn get_transaction_count(&self) -> usize; + + /// Return a transaction by its index in the container. + fn get_transaction_by_index(&self, index: usize) -> Option<&Tx>; + + fn get_transaction_iterator(&self, min_tx_id: u64, state: &mut u64) -> AppLayerGetTxIterTuple { + let mut index = *state as usize; + let len = self.get_transaction_count(); + while index < len { + let tx = self.get_transaction_by_index(index).unwrap(); + if tx.id() < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + return AppLayerGetTxIterTuple::with_values( + tx as *const _ as *mut _, + tx.id() - 1, + len - index > 1, + ); + } + return AppLayerGetTxIterTuple::not_found(); + } +} + +pub unsafe extern "C" fn state_get_tx_iterator<S: State<Tx>, Tx: Transaction>( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> AppLayerGetTxIterTuple { + let state = cast_pointer!(state, S); + state.get_transaction_iterator(min_tx_id, istate) +} + +/// AppLayerFrameType trait. +/// +/// This is the behavior expected from an enum of frame types. For most instances +/// this behavior can be derived. +/// +/// Example: +/// +/// #[derive(AppLayerFrameType)] +/// enum SomeProtoFrameType { +/// PDU, +/// Data, +/// } +pub trait AppLayerFrameType { + /// Create a frame type variant from a u8. + /// + /// None will be returned if there is no matching enum variant. + fn from_u8(value: u8) -> Option<Self> where Self: std::marker::Sized; + + /// Return the u8 value of the enum where the first entry has the value of 0. + fn as_u8(&self) -> u8; + + /// Create a frame type variant from a &str. + /// + /// None will be returned if there is no matching enum variant. + fn from_str(s: &str) -> Option<Self> where Self: std::marker::Sized; + + /// Return a pointer to a C string of the enum variant suitable as-is for + /// FFI. + fn to_cstring(&self) -> *const std::os::raw::c_char; + + /// Converts a C string formatted name to a frame type ID. + unsafe extern "C" fn ffi_id_from_name(name: *const std::os::raw::c_char) -> i32 where Self: Sized { + if name.is_null() { + return -1; + } + let frame_id = if let Ok(s) = std::ffi::CStr::from_ptr(name).to_str() { + Self::from_str(s).map(|t| t.as_u8() as i32).unwrap_or(-1) + } else { + -1 + }; + frame_id + } + + /// Converts a variant ID to an FFI safe name. + extern "C" fn ffi_name_from_id(id: u8) -> *const std::os::raw::c_char where Self: Sized { + Self::from_u8(id).map(|s| s.to_cstring()).unwrap_or_else(std::ptr::null) + } +} diff --git a/rust/src/applayertemplate/logger.rs b/rust/src/applayertemplate/logger.rs new file mode 100644 index 0000000..0105526 --- /dev/null +++ b/rust/src/applayertemplate/logger.rs @@ -0,0 +1,38 @@ +/* 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. + */ + +use super::template::TemplateTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; + +fn log_template(tx: &TemplateTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + if let Some(ref request) = tx.request { + js.set_string("request", request)?; + } + if let Some(ref response) = tx.response { + js.set_string("response", response)?; + } + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_template_logger_log( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, TemplateTransaction); + log_template(tx, js).is_ok() +} diff --git a/rust/src/applayertemplate/mod.rs b/rust/src/applayertemplate/mod.rs new file mode 100644 index 0000000..e22bd68 --- /dev/null +++ b/rust/src/applayertemplate/mod.rs @@ -0,0 +1,24 @@ +/* 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. + */ + +//! Application layer template parser and logger module. + +mod parser; +pub mod template; +/* TEMPLATE_START_REMOVE */ +pub mod logger; +/* TEMPLATE_END_REMOVE */ diff --git a/rust/src/applayertemplate/parser.rs b/rust/src/applayertemplate/parser.rs new file mode 100644 index 0000000..dc096eb --- /dev/null +++ b/rust/src/applayertemplate/parser.rs @@ -0,0 +1,64 @@ +/* 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. + */ + +use nom7::{ + bytes::streaming::{take, take_until}, + combinator::map_res, + IResult, +}; +use std; + +fn parse_len(input: &str) -> Result<u32, std::num::ParseIntError> { + input.parse::<u32>() +} + +pub fn parse_message(i: &[u8]) -> IResult<&[u8], String> { + let (i, len) = map_res(map_res(take_until(":"), std::str::from_utf8), parse_len)(i)?; + let (i, _sep) = take(1_usize)(i)?; + let (i, msg) = map_res(take(len as usize), std::str::from_utf8)(i)?; + let result = msg.to_string(); + Ok((i, result)) +} + +#[cfg(test)] +mod tests { + use super::*; + use nom7::Err; + + /// Simple test of some valid data. + #[test] + fn test_parse_valid() { + let buf = b"12:Hello World!4:Bye."; + + let result = parse_message(buf); + match result { + Ok((remainder, message)) => { + // Check the first message. + assert_eq!(message, "Hello World!"); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 6); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } +} diff --git a/rust/src/applayertemplate/template.pcap b/rust/src/applayertemplate/template.pcap Binary files differnew file mode 100644 index 0000000..00abe26 --- /dev/null +++ b/rust/src/applayertemplate/template.pcap diff --git a/rust/src/applayertemplate/template.rs b/rust/src/applayertemplate/template.rs new file mode 100644 index 0000000..acc6c26 --- /dev/null +++ b/rust/src/applayertemplate/template.rs @@ -0,0 +1,507 @@ +/* Copyright (C) 2018-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::parser; +use crate::applayer::{self, *}; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use nom7 as nom; +use std; +use std::collections::VecDeque; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; + +static mut ALPROTO_TEMPLATE: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent)] +enum TemplateEvent {} + +pub struct TemplateTransaction { + tx_id: u64, + pub request: Option<String>, + pub response: Option<String>, + + tx_data: AppLayerTxData, +} + +impl Default for TemplateTransaction { + fn default() -> Self { + Self::new() + } +} + +impl TemplateTransaction { + pub fn new() -> TemplateTransaction { + Self { + tx_id: 0, + request: None, + response: None, + tx_data: AppLayerTxData::new(), + } + } +} + +impl Transaction for TemplateTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +#[derive(Default)] +pub struct TemplateState { + state_data: AppLayerStateData, + tx_id: u64, + transactions: VecDeque<TemplateTransaction>, + request_gap: bool, + response_gap: bool, +} + +impl State<TemplateTransaction> for TemplateState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&TemplateTransaction> { + self.transactions.get(index) + } +} + +impl TemplateState { + pub fn new() -> Self { + Default::default() + } + + // Free a transaction by ID. + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&TemplateTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self) -> TemplateTransaction { + let mut tx = TemplateTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn find_request(&mut self) -> Option<&mut TemplateTransaction> { + self.transactions.iter_mut().find(|tx| tx.response.is_none()) + } + + fn parse_request(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + // If there was gap, check we can sync up again. + if self.request_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this template, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.request_gap = false; + } + + let mut start = input; + while !start.is_empty() { + match parser::parse_message(start) { + Ok((rem, request)) => { + start = rem; + + SCLogNotice!("Request: {}", request); + let mut tx = self.new_tx(); + tx.request = Some(request); + self.transactions.push_back(tx); + } + Err(nom::Err::Incomplete(_)) => { + // Not enough data. This parser doesn't give us a good indication + // of how much data is missing so just ask for one more byte so the + // parse is called as soon as more data is received. + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + + // Input was fully consumed. + return AppLayerResult::ok(); + } + + fn parse_response(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty responses. + if input.is_empty() { + return AppLayerResult::ok(); + } + + if self.response_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this template, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.response_gap = false; + } + let mut start = input; + while !start.is_empty() { + match parser::parse_message(start) { + Ok((rem, response)) => { + start = rem; + + if let Some(tx) = self.find_request() { + tx.response = Some(response); + SCLogNotice!("Found response for request:"); + SCLogNotice!("- Request: {:?}", tx.request); + SCLogNotice!("- Response: {:?}", tx.response); + } + } + Err(nom::Err::Incomplete(_)) => { + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + + // All input was fully consumed. + return AppLayerResult::ok(); + } + + fn on_request_gap(&mut self, _size: u32) { + self.request_gap = true; + } + + fn on_response_gap(&mut self, _size: u32) { + self.response_gap = true; + } +} + +/// Probe for a valid header. +/// +/// As this template protocol uses messages prefixed with the size +/// as a string followed by a ':', we look at up to the first 10 +/// characters for that pattern. +fn probe(input: &[u8]) -> nom::IResult<&[u8], ()> { + let size = std::cmp::min(10, input.len()); + let (rem, prefix) = nom::bytes::complete::take(size)(input)?; + nom::sequence::terminated( + nom::bytes::complete::take_while1(nom::character::is_digit), + nom::bytes::complete::tag(":"), + )(prefix)?; + Ok((rem, ())) +} + +// C exports. + +/// C entry point for a probing parser. +unsafe extern "C" fn rs_template_probing_parser( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + // Need at least 2 bytes. + if input_len > 1 && !input.is_null() { + let slice = build_slice!(input, input_len as usize); + if probe(slice).is_ok() { + return ALPROTO_TEMPLATE; + } + } + return ALPROTO_UNKNOWN; +} + +extern "C" fn rs_template_state_new( + _orig_state: *mut c_void, _orig_proto: AppProto, +) -> *mut c_void { + let state = TemplateState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut c_void; +} + +unsafe extern "C" fn rs_template_state_free(state: *mut c_void) { + std::mem::drop(Box::from_raw(state as *mut TemplateState)); +} + +unsafe extern "C" fn rs_template_state_tx_free(state: *mut c_void, tx_id: u64) { + let state = cast_pointer!(state, TemplateState); + state.free_tx(tx_id); +} + +unsafe extern "C" fn rs_template_parse_request( + _flow: *const Flow, state: *mut c_void, pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0; + + if eof { + // If needed, handle EOF, or pass it into the parser. + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, TemplateState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_request_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_request(buf) + } +} + +unsafe extern "C" fn rs_template_parse_response( + _flow: *const Flow, state: *mut c_void, pstate: *mut c_void, stream_slice: StreamSlice, + _data: *const c_void, +) -> AppLayerResult { + let _eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; + let state = cast_pointer!(state, TemplateState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_response_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_response(buf) + } +} + +unsafe extern "C" fn rs_template_state_get_tx(state: *mut c_void, tx_id: u64) -> *mut c_void { + let state = cast_pointer!(state, TemplateState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +unsafe extern "C" fn rs_template_state_get_tx_count(state: *mut c_void) -> u64 { + let state = cast_pointer!(state, TemplateState); + return state.tx_id; +} + +unsafe extern "C" fn rs_template_tx_get_alstate_progress(tx: *mut c_void, _direction: u8) -> c_int { + let tx = cast_pointer!(tx, TemplateTransaction); + + // Transaction is done if we have a response. + if tx.response.is_some() { + return 1; + } + return 0; +} + +/// Get the request buffer for a transaction from C. +/// +/// No required for parsing, but an example function for retrieving a +/// pointer to the request buffer from C for detection. +#[no_mangle] +pub unsafe extern "C" fn rs_template_get_request_buffer( + tx: *mut c_void, buf: *mut *const u8, len: *mut u32, +) -> u8 { + let tx = cast_pointer!(tx, TemplateTransaction); + if let Some(ref request) = tx.request { + if !request.is_empty() { + *len = request.len() as u32; + *buf = request.as_ptr(); + return 1; + } + } + return 0; +} + +/// Get the response buffer for a transaction from C. +#[no_mangle] +pub unsafe extern "C" fn rs_template_get_response_buffer( + tx: *mut c_void, buf: *mut *const u8, len: *mut u32, +) -> u8 { + let tx = cast_pointer!(tx, TemplateTransaction); + if let Some(ref response) = tx.response { + if !response.is_empty() { + *len = response.len() as u32; + *buf = response.as_ptr(); + return 1; + } + } + return 0; +} + +export_tx_data_get!(rs_template_get_tx_data, TemplateTransaction); +export_state_data_get!(rs_template_get_state_data, TemplateState); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"template\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_template_register_parser() { + /* TEMPLATE_START_REMOVE */ + if crate::conf::conf_get_node("app-layer.protocols.template").is_none() { + return; + } + /* TEMPLATE_END_REMOVE */ + + let default_port = CString::new("[7000]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_template_probing_parser), + probe_tc: Some(rs_template_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_template_state_new, + state_free: rs_template_state_free, + tx_free: rs_template_state_tx_free, + parse_ts: rs_template_parse_request, + parse_tc: rs_template_parse_response, + get_tx_count: rs_template_state_get_tx_count, + get_tx: rs_template_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_template_tx_get_alstate_progress, + get_eventinfo: Some(TemplateEvent::get_event_info), + get_eventinfo_byid: Some(TemplateEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some( + applayer::state_get_tx_iterator::<TemplateState, TemplateTransaction>, + ), + get_tx_data: rs_template_get_tx_data, + get_state_data: rs_template_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_TEMPLATE = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogNotice!("Rust template parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for TEMPLATE."); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_probe() { + assert!(probe(b"1").is_err()); + assert!(probe(b"1:").is_ok()); + assert!(probe(b"123456789:").is_ok()); + assert!(probe(b"0123456789:").is_err()); + } + + #[test] + fn test_incomplete() { + let mut state = TemplateState::new(); + let buf = b"5:Hello3:bye"; + + let r = state.parse_request(&buf[0..0]); + assert_eq!( + r, + AppLayerResult { + status: 0, + consumed: 0, + needed: 0 + } + ); + + let r = state.parse_request(&buf[0..1]); + assert_eq!( + r, + AppLayerResult { + status: 1, + consumed: 0, + needed: 2 + } + ); + + let r = state.parse_request(&buf[0..2]); + assert_eq!( + r, + AppLayerResult { + status: 1, + consumed: 0, + needed: 3 + } + ); + + // This is the first message and only the first message. + let r = state.parse_request(&buf[0..7]); + assert_eq!( + r, + AppLayerResult { + status: 0, + consumed: 0, + needed: 0 + } + ); + + // The first message and a portion of the second. + let r = state.parse_request(&buf[0..9]); + assert_eq!( + r, + AppLayerResult { + status: 1, + consumed: 7, + needed: 3 + } + ); + } +} diff --git a/rust/src/asn1/mod.rs b/rust/src/asn1/mod.rs new file mode 100644 index 0000000..4b77b0c --- /dev/null +++ b/rust/src/asn1/mod.rs @@ -0,0 +1,393 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! ASN.1 parser module. + +use der_parser::ber::{parse_ber_recursive, BerObject, BerObjectContent, Tag}; +use nom7::Err; +use std::convert::TryFrom; + +mod parse_rules; +use parse_rules::DetectAsn1Data; + +/// Container for parsed Asn1 objects +#[derive(Debug)] +pub struct Asn1<'a>(Vec<BerObject<'a>>); + +/// Errors possible during decoding of Asn1 +#[derive(Debug)] +enum Asn1DecodeError { + InvalidKeywordParameter, + MaxFrames, + BerError(Err<der_parser::error::BerError>), +} + +/// Enumeration of Asn1 checks +#[derive(Debug, PartialEq)] +enum Asn1Check { + OversizeLength, + BitstringOverflow, + DoubleOverflow, + MaxDepth, +} + +impl<'a> Asn1<'a> { + /// Checks each BerObject contained in self with the provided detection + /// data, returns the first successful match if one occurs + fn check(&self, ad: &DetectAsn1Data) -> Option<Asn1Check> { + for obj in &self.0 { + let res = Asn1::check_object_recursive(obj, ad, ad.max_frames as usize); + if res.is_some() { + return res; + } + } + + None + } + + fn check_object_recursive( + obj: &BerObject, ad: &DetectAsn1Data, max_depth: usize, + ) -> Option<Asn1Check> { + // Check stack depth + if max_depth == 0 { + return Some(Asn1Check::MaxDepth); + } + + // Check current object + let res = Asn1::check_object(obj, ad); + if res.is_some() { + return res; + } + + // Check sub-nodes + for node in obj.ref_iter() { + let res = Asn1::check_object_recursive(node, ad, max_depth - 1); + if res.is_some() { + return res; + } + } + + None + } + + /// Checks a BerObject and subnodes against the Asn1 checks + fn check_object(obj: &BerObject, ad: &DetectAsn1Data) -> Option<Asn1Check> { + // get length + // Note that if length is indefinite (BER), this will return None + let len = obj.header.length().definite().ok()?; + // oversize_length will check if a node has a length greater than + // the user supplied length + if let Some(oversize_length) = ad.oversize_length { + if len > oversize_length as usize + || obj.content.as_slice().unwrap_or(&[]).len() > oversize_length as usize + { + return Some(Asn1Check::OversizeLength); + } + } + + // bitstring_overflow check a malformed option where the number of bits + // to ignore is greater than the length decoded (in bits) + if ad.bitstring_overflow + && (obj.header.is_universal() + && obj.header.tag() == Tag::BitString + && obj.header.is_primitive()) + { + if let BerObjectContent::BitString(bits, _v) = &obj.content { + if len > 0 + && *bits as usize > len.saturating_mul(8) + { + return Some(Asn1Check::BitstringOverflow); + } + } + } + + // double_overflow checks a known issue that affects the MSASN1 library + // when decoding double/real types. If the encoding is ASCII, + // and the buffer is greater than 256, the array is overflown + if ad.double_overflow + && (obj.header.is_universal() + && obj.header.tag() == Tag::RealType + && obj.header.is_primitive()) + { + if let Ok(data) = obj.content.as_slice() { + if len > 0 + && !data.is_empty() + && data[0] & 0xC0 == 0 + && (len > 256 || data.len() > 256) + { + return Some(Asn1Check::DoubleOverflow); + } + } + } + + None + } + + fn from_slice(input: &'a [u8], ad: &DetectAsn1Data) -> Result<Asn1<'a>, Asn1DecodeError> { + let mut results = Vec::new(); + let mut rest = input; + + // while there's data to process + while !rest.is_empty() { + let max_depth = ad.max_frames as usize; + + if results.len() >= max_depth { + return Err(Asn1DecodeError::MaxFrames); + } + + let res = parse_ber_recursive(rest, max_depth); + + match res { + Ok((new_rest, obj)) => { + results.push(obj); + + rest = new_rest; + } + // If there's an error, bail + Err(_) => { + // silent error as this could fail + // on non-asn1 or fragmented packets + break; + } + } + } + + Ok(Asn1(results)) + } +} + +/// Decodes Asn1 objects from an input + length while applying the offset +/// defined in the asn1 keyword options +fn asn1_decode<'a>( + buffer: &'a [u8], buffer_offset: u32, ad: &DetectAsn1Data, +) -> Result<Asn1<'a>, Asn1DecodeError> { + // Get offset + let offset = if let Some(absolute_offset) = ad.absolute_offset { + absolute_offset + } else if let Some(relative_offset) = ad.relative_offset { + // relative offset in regards to the last content match + + // buffer_offset (u32) + relative_offset (i32) => offset (u16) + u16::try_from({ + if relative_offset > 0 { + buffer_offset + .checked_add(u32::try_from(relative_offset)?) + .ok_or(Asn1DecodeError::InvalidKeywordParameter)? + } else { + buffer_offset + .checked_sub(u32::try_from(-relative_offset)?) + .ok_or(Asn1DecodeError::InvalidKeywordParameter)? + } + }) + .or(Err(Asn1DecodeError::InvalidKeywordParameter))? + } else { + 0 + }; + + // Make sure we won't read past the end or front of the buffer + if offset as usize >= buffer.len() { + return Err(Asn1DecodeError::InvalidKeywordParameter); + } + + // Get slice from buffer at offset + let slice = &buffer[offset as usize..]; + + Asn1::from_slice(slice, ad) +} + +/// Attempt to parse a Asn1 object from input, and return a pointer +/// to the parsed object if successful, null on failure +/// +/// # Safety +/// +/// input must be a valid buffer of at least input_len bytes +/// pointer must be freed using `rs_asn1_free` +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_decode( + input: *const u8, input_len: u16, buffer_offset: u32, ad_ptr: *const DetectAsn1Data, +) -> *mut Asn1<'static> { + if input.is_null() || input_len == 0 || ad_ptr.is_null() { + return std::ptr::null_mut(); + } + + let slice = build_slice!(input, input_len as usize); + + let ad = &*ad_ptr ; + + let res = asn1_decode(slice, buffer_offset, ad); + + match res { + Ok(asn1) => Box::into_raw(Box::new(asn1)), + Err(_e) => std::ptr::null_mut(), + } +} + +/// Free a Asn1 object allocated by Rust +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_asn1_decode` +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_free(ptr: *mut Asn1) { + if ptr.is_null() { + return; + } + drop(Box::from_raw(ptr)); +} + +/// This function implements the detection of the following options: +/// - oversize_length +/// - bitstring_overflow +/// - double_overflow +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_asn1_decode` +/// ad_ptr must be a valid object obtained using `rs_detect_asn1_parse` +/// +/// Returns 1 if any of the options match, 0 if not +#[no_mangle] +pub unsafe extern "C" fn rs_asn1_checks(ptr: *const Asn1, ad_ptr: *const DetectAsn1Data) -> u8 { + if ptr.is_null() || ad_ptr.is_null() { + return 0; + } + + let asn1 = &*ptr; + let ad = &*ad_ptr; + + match asn1.check(ad) { + Some(_check) => 1, + None => 0, + } +} + +impl From<std::num::TryFromIntError> for Asn1DecodeError { + fn from(_e: std::num::TryFromIntError) -> Asn1DecodeError { + Asn1DecodeError::InvalidKeywordParameter + } +} + +impl From<Err<der_parser::error::BerError>> for Asn1DecodeError { + fn from(e: Err<der_parser::error::BerError>) -> Asn1DecodeError { + Asn1DecodeError::BerError(e) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // Example from the specification X.690-0207 Appendix A.3 + static ASN1_A3: &[u8] = b"\x60\x81\x85\x61\x10\x1A\x04John\x1A\x01 \ + P\x1A\x05Smith\xA0\x0A\x1A\x08Director \ + \x42\x01\x33\xA1\x0A\x43\x0819710917 \ + \xA2\x12\x61\x10\x1A\x04Mary\x1A\x01T\x1A\x05 \ + Smith\xA3\x42\x31\x1F\x61\x11\x1A\x05Ralph\x1A\x01 \ + T\x1A\x05Smith\xA0\x0A\x43\x0819571111 \ + \x31\x1F\x61\x11\x1A\x05Susan\x1A\x01B\x1A\x05 \ + Jones\xA0\x0A\x43\x0819590717"; + + /// Ensure that the checks work when they should + #[test_case("oversize_length 132 absolute_offset 0", ASN1_A3, DetectAsn1Data { + oversize_length: Some(132), + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::OversizeLength); "Test oversize_length rule (match)" )] + #[test_case("oversize_length 133 absolute_offset 0", ASN1_A3, DetectAsn1Data { + oversize_length: Some(133), + absolute_offset: Some(0), + ..Default::default() + }, None; "Test oversize_length rule (non-match)" )] + #[test_case("bitstring_overflow, absolute_offset 0", + /* tagnum bitstring, primitive, and as universal tag, + length = 1 octet, but the next octet specify to ignore the last 256 bits */ + b"\x03\x01\xFF", + DetectAsn1Data { + bitstring_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::BitstringOverflow); "Test bitstring_overflow rule (match)" )] + #[test_case("bitstring_overflow, absolute_offset 0", + /* tagnum bitstring, primitive, and as universal tag, + length = 1 octet, but the next octet specify to ignore the last 7 bits */ + b"\x03\x01\x07", + DetectAsn1Data { + bitstring_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, None; "Test bitstring_overflow rule (non-match)" )] + #[test_case("double_overflow, absolute_offset 0", + { + static TEST_BUF: [u8; 261] = { + let mut b = [0x05; 261]; + /* universal class, primitive type, tag_num = 9 (Data type Real) */ + b[0] = 0x09; + /* length, definite form, 2 octets */ + b[1] = 0x82; + /* length is the sum of the following octets (257): */ + b[2] = 0x01; + b[3] = 0x01; + + b + }; + + &TEST_BUF + }, + DetectAsn1Data { + double_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, Some(Asn1Check::DoubleOverflow); "Test double_overflow rule (match)" )] + #[test_case("double_overflow, absolute_offset 0", + { + static TEST_BUF: [u8; 261] = { + let mut b = [0x05; 261]; + /* universal class, primitive type, tag_num = 9 (Data type Real) */ + b[0] = 0x09; + /* length, definite form, 2 octets */ + b[1] = 0x82; + /* length is the sum of the following octets (256): */ + b[2] = 0x01; + b[3] = 0x00; + + b + }; + + &TEST_BUF + }, + DetectAsn1Data { + double_overflow: true, + absolute_offset: Some(0), + ..Default::default() + }, None; "Test double_overflow rule (non-match)" )] + fn test_checks( + rule: &str, asn1_buf: &'static [u8], expected_data: DetectAsn1Data, + expected_check: Option<Asn1Check>, + ) { + // Parse rule + let (_rest, ad) = parse_rules::asn1_parse_rule(rule).unwrap(); + assert_eq!(expected_data, ad); + + // Decode + let asn1 = Asn1::from_slice(asn1_buf, &ad).unwrap(); + + // Run checks + let result = asn1.check(&ad); + assert_eq!(expected_check, result); + } +} diff --git a/rust/src/asn1/parse_rules.rs b/rust/src/asn1/parse_rules.rs new file mode 100644 index 0000000..540734c --- /dev/null +++ b/rust/src/asn1/parse_rules.rs @@ -0,0 +1,326 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::branch::alt; +use nom7::bytes::complete::tag; +use nom7::character::complete::{digit1, multispace0, multispace1}; +use nom7::combinator::{map_res, opt, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::sequence::{separated_pair, tuple}; +use nom7::{Err, IResult}; +use std::ffi::CStr; +use std::os::raw::c_char; + +const ASN1_DEFAULT_MAX_FRAMES: u16 = 30; + +/// Parse the asn1 keyword and return a pointer to a `DetectAsn1Data` +/// containing the parsed options, returns null on failure +/// +/// # Safety +/// +/// pointer must be free'd using `rs_detect_asn1_free` +#[no_mangle] +pub unsafe extern "C" fn rs_detect_asn1_parse(input: *const c_char) -> *mut DetectAsn1Data { + if input.is_null() { + return std::ptr::null_mut(); + } + + let arg = match CStr::from_ptr(input).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match asn1_parse_rule(arg) { + Ok((_rest, data)) => { + let mut data = data; + + // Get configuration value + if let Some(max_frames) = crate::conf::conf_get("asn1-max-frames") { + if let Ok(v) = max_frames.parse::<u16>() { + data.max_frames = v; + } else { + SCLogError!("Could not parse asn1-max-frames: {}", max_frames); + return std::ptr::null_mut(); + }; + } + + Box::into_raw(Box::new(data)) + } + Err(e) => { + SCLogError!("Malformed asn1 argument: {}", e.to_string()); + std::ptr::null_mut() + } + } +} + +/// Free a `DetectAsn1Data` object allocated by Rust +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_detect_asn1_parse` +#[no_mangle] +pub unsafe extern "C" fn rs_detect_asn1_free(ptr: *mut DetectAsn1Data) { + if ptr.is_null() { + return; + } + drop(Box::from_raw(ptr)); +} + +/// Struct to hold parsed asn1 keyword options +#[derive(Debug, PartialEq, Eq)] +pub struct DetectAsn1Data { + pub bitstring_overflow: bool, + pub double_overflow: bool, + pub oversize_length: Option<u32>, + pub absolute_offset: Option<u16>, + pub relative_offset: Option<i32>, + pub max_frames: u16, +} + +impl Default for DetectAsn1Data { + fn default() -> DetectAsn1Data { + DetectAsn1Data { + bitstring_overflow: false, + double_overflow: false, + oversize_length: None, + absolute_offset: None, + relative_offset: None, + max_frames: ASN1_DEFAULT_MAX_FRAMES, + } + } +} + +fn parse_u32_number(input: &str) -> IResult<&str, u32> { + map_res(digit1, |digits: &str| digits.parse::<u32>())(input) +} + +fn parse_u16_number(input: &str) -> IResult<&str, u16> { + map_res(digit1, |digits: &str| digits.parse::<u16>())(input) +} + +fn parse_i32_number(input: &str) -> IResult<&str, i32> { + let (rest, negate) = opt(tag("-"))(input)?; + let (rest, d) = map_res(digit1, |s: &str| s.parse::<i32>())(rest)?; + let n = if negate.is_some() { -1 } else { 1 }; + Ok((rest, d * n)) +} + +/// Parse asn1 keyword options +pub(super) fn asn1_parse_rule(input: &str) -> IResult<&str, DetectAsn1Data> { + // If nothing to parse, return + if input.is_empty() { + return Err(Err::Error(make_error( + input, + ErrorKind::Eof, + ))); + } + + // Rule parsing functions + fn bitstring_overflow(i: &str) -> IResult<&str, &str> { + tag("bitstring_overflow")(i) + } + + fn double_overflow(i: &str) -> IResult<&str, &str> { + tag("double_overflow")(i) + } + + fn oversize_length(i: &str) -> IResult<&str, (&str, u32)> { + separated_pair(tag("oversize_length"), multispace1, parse_u32_number)(i) + } + + fn absolute_offset(i: &str) -> IResult<&str, (&str, u16)> { + separated_pair(tag("absolute_offset"), multispace1, parse_u16_number)(i) + } + + fn relative_offset(i: &str) -> IResult<&str, (&str, i32)> { + separated_pair( + tag("relative_offset"), + multispace1, + verify(parse_i32_number, |v| { + *v >= -i32::from(std::u16::MAX) && *v <= i32::from(std::u16::MAX) + }), + )(i) + } + + let mut data = DetectAsn1Data::default(); + + let mut rest = input; + + // Parse the input and set data + while !rest.is_empty() { + let ( + new_rest, + ( + _, + bitstring_overflow, + double_overflow, + oversize_length, + absolute_offset, + relative_offset, + _, + ), + ) = tuple(( + opt(multispace0), + opt(bitstring_overflow), + opt(double_overflow), + opt(oversize_length), + opt(absolute_offset), + opt(relative_offset), + opt(alt((multispace1, tag(",")))), + ))(rest)?; + + if bitstring_overflow.is_some() { + data.bitstring_overflow = true; + } else if double_overflow.is_some() { + data.double_overflow = true; + } else if let Some((_, v)) = oversize_length { + data.oversize_length = Some(v); + } else if let Some((_, v)) = absolute_offset { + data.absolute_offset = Some(v); + } else if let Some((_, v)) = relative_offset { + data.relative_offset = Some(v); + } else { + return Err(Err::Error(make_error( + rest, + ErrorKind::Verify, + ))); + } + + rest = new_rest; + } + + Ok((rest, data)) +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + // Test oversize_length + #[test_case("oversize_length 1024", + DetectAsn1Data { oversize_length: Some(1024), ..Default::default()}; + "check that we parse oversize_length correctly")] + #[test_case("oversize_length 0", + DetectAsn1Data { oversize_length: Some(0), ..Default::default()}; + "check lower bound on oversize_length")] + #[test_case("oversize_length -1", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length -1", code: Verify }"#; + "check under lower bound on oversize_length")] + #[test_case("oversize_length 4294967295", + DetectAsn1Data { oversize_length: Some(4294967295), ..Default::default()}; + "check upper bound on oversize_length")] + #[test_case("oversize_length 4294967296", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length 4294967296", code: Verify }"#; + "check over upper bound on oversize_length")] + #[test_case("oversize_length", + DetectAsn1Data::default() => panics r#"Error { input: "oversize_length", code: Verify }"#; + "check that we fail if the needed arg oversize_length is not given")] + // Test absolute_offset + #[test_case("absolute_offset 1024", + DetectAsn1Data { absolute_offset: Some(1024), ..Default::default()}; + "check that we parse absolute_offset correctly")] + #[test_case("absolute_offset 0", + DetectAsn1Data { absolute_offset: Some(0), ..Default::default()}; + "check lower bound on absolute_offset")] + #[test_case("absolute_offset -1", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset -1", code: Verify }"#; + "check under lower bound on absolute_offset")] + #[test_case("absolute_offset 65535", + DetectAsn1Data { absolute_offset: Some(65535), ..Default::default()}; + "check upper bound on absolute_offset")] + #[test_case("absolute_offset 65536", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset 65536", code: Verify }"#; + "check over upper bound on absolute_offset")] + #[test_case("absolute_offset", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#; + "check that we fail if the needed arg absolute_offset is not given")] + // Test relative_offset + #[test_case("relative_offset 1024", + DetectAsn1Data { relative_offset: Some(1024), ..Default::default()}; + "check that we parse relative_offset correctly")] + #[test_case("relative_offset -65535", + DetectAsn1Data { relative_offset: Some(-65535), ..Default::default()}; + "check lower bound on relative_offset")] + #[test_case("relative_offset -65536", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset -65536", code: Verify }"#; + "check under lower bound on relative_offset")] + #[test_case("relative_offset 65535", + DetectAsn1Data { relative_offset: Some(65535), ..Default::default()}; + "check upper bound on relative_offset")] + #[test_case("relative_offset 65536", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset 65536", code: Verify }"#; + "check over upper bound on relative_offset")] + #[test_case("relative_offset", + DetectAsn1Data::default() => panics r#"Error { input: "relative_offset", code: Verify }"#; + "check that we fail if the needed arg relative_offset is not given")] + // Test bitstring_overflow + #[test_case("bitstring_overflow", + DetectAsn1Data { bitstring_overflow: true, ..Default::default()}; + "check that we parse bitstring_overflow correctly")] + // Test double_overflow + #[test_case("double_overflow", + DetectAsn1Data { double_overflow: true, ..Default::default()}; + "check that we parse double_overflow correctly")] + // Test combination of params + #[test_case("oversize_length 1024, relative_offset 10", + DetectAsn1Data { oversize_length: Some(1024), relative_offset: Some(10), + ..Default::default()}; + "check for combinations of keywords (comma seperated)")] + #[test_case("oversize_length 1024 absolute_offset 10", + DetectAsn1Data { oversize_length: Some(1024), absolute_offset: Some(10), + ..Default::default()}; + "check for combinations of keywords (space seperated)")] + #[test_case("oversize_length 1024 absolute_offset 10, bitstring_overflow", + DetectAsn1Data { bitstring_overflow: true, oversize_length: Some(1024), + absolute_offset: Some(10), ..Default::default()}; + "check for combinations of keywords (space/comma seperated)")] + #[test_case( + "double_overflow, oversize_length 1024 absolute_offset 10,\n bitstring_overflow", + DetectAsn1Data { double_overflow: true, bitstring_overflow: true, + oversize_length: Some(1024), absolute_offset: Some(10), + ..Default::default()}; + "1. check for combinations of keywords (space/comma/newline seperated)")] + #[test_case( + "\n\t double_overflow, oversize_length 1024 relative_offset 10,\n bitstring_overflow", + DetectAsn1Data { double_overflow: true, bitstring_overflow: true, + oversize_length: Some(1024), relative_offset: Some(10), + ..Default::default()}; + "2. check for combinations of keywords (space/comma/newline seperated)")] + // Test empty + #[test_case("", + DetectAsn1Data::default() => panics r#"Error { input: "", code: Eof }"#; + "test that we break with a empty string")] + // Test invalid rules + #[test_case("oversize_length 1024, some_other_param 360", + DetectAsn1Data::default() => panics r#"Error { input: " some_other_param 360", code: Verify }"#; + "test that we break on invalid options")] + #[test_case("oversize_length 1024,,", + DetectAsn1Data::default() => panics r#"Error { input: ",", code: Verify }"#; + "test that we break on invalid format (missing option)")] + #[test_case("bitstring_overflowabsolute_offset", + DetectAsn1Data::default() => panics r#"Error { input: "absolute_offset", code: Verify }"#; + "test that we break on invalid format (missing separator)")] + fn test_asn1_parse_rule(input: &str, expected: DetectAsn1Data) { + let (rest, res) = asn1_parse_rule(input).unwrap(); + + assert_eq!(0, rest.len()); + assert_eq!(expected, res); + } +} diff --git a/rust/src/bittorrent_dht/bittorrent_dht.rs b/rust/src/bittorrent_dht/bittorrent_dht.rs new file mode 100644 index 0000000..8c6857d --- /dev/null +++ b/rust/src/bittorrent_dht/bittorrent_dht.rs @@ -0,0 +1,326 @@ +/* Copyright (C) 2021-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::applayer::{self, *}; +use crate::bittorrent_dht::parser::{ + parse_bittorrent_dht_packet, BitTorrentDHTError, BitTorrentDHTRequest, BitTorrentDHTResponse, +}; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_UDP, Direction}; +use std::ffi::CString; +use std::os::raw::c_char; + +const BITTORRENT_DHT_PAYLOAD_PREFIX: &[u8] = b"d1:ad2:id20:\0"; + +static mut ALPROTO_BITTORRENT_DHT: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent, Debug, PartialEq, Eq)] +pub enum BitTorrentDHTEvent { + MalformedPacket, +} + +#[derive(Default)] +pub struct BitTorrentDHTTransaction { + tx_id: u64, + pub request_type: Option<String>, + pub request: Option<BitTorrentDHTRequest>, + pub response: Option<BitTorrentDHTResponse>, + pub error: Option<BitTorrentDHTError>, + pub transaction_id: Vec<u8>, + pub client_version: Option<Vec<u8>>, + + tx_data: AppLayerTxData, +} + +impl BitTorrentDHTTransaction { + pub fn new(direction: Direction) -> Self { + Self { + tx_data: AppLayerTxData::for_direction(direction), + ..Default::default() + } + } + + /// Set an event on the transaction + pub fn set_event(&mut self, event: BitTorrentDHTEvent) { + self.tx_data.set_event(event as u8); + } +} + +#[derive(Default)] +pub struct BitTorrentDHTState { + tx_id: u64, + transactions: Vec<BitTorrentDHTTransaction>, + state_data: AppLayerStateData, +} + +impl BitTorrentDHTState { + pub fn new() -> Self { + Self::default() + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + self.transactions.retain(|tx| tx.tx_id != tx_id + 1); + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&BitTorrentDHTTransaction> { + self.transactions.iter().find(|&tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self, direction: Direction) -> BitTorrentDHTTransaction { + let mut tx = BitTorrentDHTTransaction::new(direction); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn is_dht(input: &[u8]) -> bool { + if input.len() > 5 { + match &input[0..5] { + b"d1:ad" | b"d1:rd" | b"d2:ip" | b"d1:el" => true, + _ => false, + } + } else { + false + } + } + + pub fn parse(&mut self, input: &[u8], _direction: crate::core::Direction) -> bool { + if !Self::is_dht(input) { + return true; + } + let mut tx = self.new_tx(_direction); + let mut status = true; + + if let Err(_e) = parse_bittorrent_dht_packet(input, &mut tx) { + status = false; + tx.set_event(BitTorrentDHTEvent::MalformedPacket); + SCLogDebug!("BitTorrent DHT Parsing Error: {}", _e); + } + + self.transactions.push(tx); + + return status; + } + + fn tx_iterator( + &mut self, min_tx_id: u64, state: &mut u64, + ) -> Option<(&BitTorrentDHTTransaction, u64, bool)> { + let mut index = *state as usize; + let len = self.transactions.len(); + + while index < len { + let tx = &self.transactions[index]; + if tx.tx_id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + return Some((tx, tx.tx_id - 1, (len - index) > 1)); + } + + return None; + } +} + +// C exports. + +export_tx_data_get!(rs_bittorrent_dht_get_tx_data, BitTorrentDHTTransaction); +export_state_data_get!(rs_bittorrent_dht_get_state_data, BitTorrentDHTState); + +#[no_mangle] +pub extern "C" fn rs_bittorrent_dht_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = BitTorrentDHTState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut std::os::raw::c_void; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut BitTorrentDHTState)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_tx_free( + state: *mut std::os::raw::c_void, tx_id: u64, +) { + let state = cast_pointer!(state, BitTorrentDHTState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_parse_ts( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + return rs_bittorrent_dht_parse( + _flow, state, _pstate, stream_slice, + _data, crate::core::Direction::ToServer); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_parse_tc( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + return rs_bittorrent_dht_parse( + _flow, state, _pstate, stream_slice, + _data, crate::core::Direction::ToClient); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_parse( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, + direction: crate::core::Direction, +) -> AppLayerResult { + let state = cast_pointer!(state, BitTorrentDHTState); + let buf = stream_slice.as_slice(); + state.parse(buf, direction).into() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, BitTorrentDHTState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx_count( + state: *mut std::os::raw::c_void, +) -> u64 { + let state = cast_pointer!(state, BitTorrentDHTState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, BitTorrentDHTTransaction); + + // Transaction is done if we have a request, response, or error since + // a new transaction is created for each received packet + if tx.request.is_some() || tx.response.is_some() || tx.error.is_some() { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_state_get_tx_iterator( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, BitTorrentDHTState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = tx as *const _ as *mut _; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"bittorrent-dht\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_udp_register_parser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_UDP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_bittorrent_dht_state_new, + state_free: rs_bittorrent_dht_state_free, + tx_free: rs_bittorrent_dht_state_tx_free, + parse_ts: rs_bittorrent_dht_parse_ts, + parse_tc: rs_bittorrent_dht_parse_tc, + get_tx_count: rs_bittorrent_dht_state_get_tx_count, + get_tx: rs_bittorrent_dht_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_bittorrent_dht_tx_get_alstate_progress, + get_eventinfo: Some(BitTorrentDHTEvent::get_event_info), + get_eventinfo_byid: Some(BitTorrentDHTEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(rs_bittorrent_dht_state_get_tx_iterator), + get_tx_data: rs_bittorrent_dht_get_tx_data, + get_state_data: rs_bittorrent_dht_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_BITTORRENT_DHT = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + + if AppLayerProtoDetectPMRegisterPatternCS( + IPPROTO_UDP, + ALPROTO_BITTORRENT_DHT, + BITTORRENT_DHT_PAYLOAD_PREFIX.as_ptr() as *const c_char, + BITTORRENT_DHT_PAYLOAD_PREFIX.len() as u16 - 1, + 0, + crate::core::Direction::ToServer.into(), + ) < 0 + { + SCLogDebug!("Failed to register protocol detection pattern for direction TOSERVER"); + }; + if AppLayerProtoDetectPMRegisterPatternCS( + IPPROTO_UDP, + ALPROTO_BITTORRENT_DHT, + BITTORRENT_DHT_PAYLOAD_PREFIX.as_ptr() as *const c_char, + BITTORRENT_DHT_PAYLOAD_PREFIX.len() as u16 - 1, + 0, + crate::core::Direction::ToClient.into(), + ) < 0 + { + SCLogDebug!("Failed to register protocol detection pattern for direction TOCLIENT"); + } + + SCLogDebug!("Parser registered for bittorrent-dht."); + } else { + SCLogDebug!("Protocol detector and parser disabled for bittorrent-dht."); + } +} diff --git a/rust/src/bittorrent_dht/logger.rs b/rust/src/bittorrent_dht/logger.rs new file mode 100644 index 0000000..2cfb927 --- /dev/null +++ b/rust/src/bittorrent_dht/logger.rs @@ -0,0 +1,137 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::bittorrent_dht::BitTorrentDHTTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +/// Format bytes as an IP address string. +fn print_ip_addr(addr: &[u8]) -> std::string::String { + if addr.len() == 4 { + return format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]); + } else if addr.len() == 16 { + return format!("{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5], + addr[6], + addr[7], + addr[8], + addr[9], + addr[10], + addr[11], + addr[12], + addr[13], + addr[14], + addr[15]); + } else { + return "".to_string(); + } +} + +fn log_bittorrent_dht( + tx: &BitTorrentDHTTransaction, js: &mut JsonBuilder, +) -> Result<(), JsonError> { + js.set_hex("transaction_id", &tx.transaction_id)?; + if let Some(client_version) = &tx.client_version { + js.set_hex("client_version", client_version)?; + } + if let Some(request_type) = &tx.request_type { + js.set_string("request_type", request_type)?; + } + if let Some(error) = &tx.error { + js.open_object("error")?; + js.set_uint("num", u64::from(error.num))?; + js.set_string("msg", &error.msg)?; + js.close()?; + }; + if let Some(request) = &tx.request { + js.open_object("request")?; + js.set_hex("id", &request.id)?; + if let Some(target) = &request.target { + js.set_hex("target", target)?; + } + if let Some(info_hash) = &request.info_hash { + js.set_hex("info_hash", info_hash)?; + } + if let Some(token) = &request.token { + js.set_hex("token", token)?; + } + if let Some(implied_port) = request.implied_port { + js.set_uint("implied_port", u64::from(implied_port))?; + } + if let Some(port) = request.port { + js.set_uint("port", u64::from(port))?; + } + js.close()?; + }; + if let Some(response) = &tx.response { + js.open_object("response")?; + js.set_hex("id", &response.id)?; + if let Some(nodes) = &response.nodes { + if !nodes.is_empty() { + js.open_array("nodes")?; + for node in nodes { + js.start_object()?; + js.set_hex("id", &node.id)?; + js.set_string("ip", &print_ip_addr(&node.ip))?; + js.set_uint("port", node.port.into())?; + js.close()?; + } + js.close()?; + } + } + if let Some(nodes) = &response.nodes6 { + if !nodes.is_empty() { + js.open_array("nodes6")?; + for node in nodes { + js.start_object()?; + js.set_hex("id", &node.id)?; + js.set_string("ip", &print_ip_addr(&node.ip))?; + js.set_uint("port", node.port.into())?; + js.close()?; + } + js.close()?; + } + } + if let Some(values) = &response.values { + js.open_array("values")?; + for value in values { + js.start_object()?; + js.set_string("ip", &print_ip_addr(&value.ip))?; + js.set_uint("port", value.port.into())?; + js.close()?; + } + js.close()?; + } + if let Some(token) = &response.token { + js.set_hex("token", token)?; + } + js.close()?; + }; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_bittorrent_dht_logger_log( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, BitTorrentDHTTransaction); + log_bittorrent_dht(tx, js).is_ok() +} diff --git a/rust/src/bittorrent_dht/mod.rs b/rust/src/bittorrent_dht/mod.rs new file mode 100644 index 0000000..9e0d033 --- /dev/null +++ b/rust/src/bittorrent_dht/mod.rs @@ -0,0 +1,22 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! BitTorrent DHT application layer, logger and parser module. + +pub mod bittorrent_dht; +pub mod logger; +pub mod parser; diff --git a/rust/src/bittorrent_dht/parser.rs b/rust/src/bittorrent_dht/parser.rs new file mode 100644 index 0000000..545a1ad --- /dev/null +++ b/rust/src/bittorrent_dht/parser.rs @@ -0,0 +1,663 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/*! Parses BitTorrent DHT specification BEP_0005 + * <https://www.bittorrent.org/beps/bep_0005.html> !*/ + +// TODO: Custom error type, as we have bencode and nom errors, and may have an our application +// specific errors as we finish off this parser. + +use crate::bittorrent_dht::bittorrent_dht::BitTorrentDHTTransaction; +use bendy::decoding::{Decoder, Error, FromBencode, Object, ResultExt}; +use nom7::bytes::complete::take; +use nom7::number::complete::be_u16; +use nom7::IResult; + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTRequest { + /// q = * - 20 byte string, sender's node ID in network byte order + pub id: Vec<u8>, + /// q = find_node - target node ID + pub target: Option<Vec<u8>>, + /// q = get_peers/announce_peer - 20-byte info hash of target torrent + pub info_hash: Option<Vec<u8>>, + /// q = announce_peer - token key received from previous get_peers query + pub token: Option<Vec<u8>>, + /// q = announce_peer - 0 or 1, if 1 ignore provided port and + /// use source port of UDP packet + pub implied_port: Option<u8>, + /// q = announce_peer - port on which peer will download torrent + pub port: Option<u16>, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTResponse { + /// q = * - 20 byte string, receiver's node ID in network byte order + pub id: Vec<u8>, + /// q = find_node/get_peers - compact node info for target node or + /// K(8) closest good nodes in routing table + pub nodes: Option<Vec<Node>>, + pub nodes6: Option<Vec<Node>>, + /// q = get_peers - list of compact peer infos + pub values: Option<Vec<Peer>>, + /// q = get_peers - token key required for sender's future + /// announce_peer query + pub token: Option<Vec<u8>>, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct BitTorrentDHTError { + /// integer representing the error code + pub num: u16, + /// string containing the error message + pub msg: String, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Node { + pub id: Vec<u8>, + pub ip: Vec<u8>, + pub port: u16, +} + +#[derive(Debug, Eq, PartialEq)] +pub struct Peer { + pub ip: Vec<u8>, + pub port: u16, +} + +/// Parse IPv4 node structures. +pub fn parse_node(i: &[u8]) -> IResult<&[u8], Node> { + let (i, id) = take(20usize)(i)?; + let (i, ip) = take(4usize)(i)?; + let (i, port) = be_u16(i)?; + Ok(( + i, + Node { + id: id.to_vec(), + ip: ip.to_vec(), + port, + }, + )) +} + +/// Parse IPv6 node structures. +pub fn parse_node6(i: &[u8]) -> IResult<&[u8], Node> { + let (i, id) = take(20usize)(i)?; + let (i, ip) = take(16usize)(i)?; + let (i, port) = be_u16(i)?; + Ok(( + i, + Node { + id: id.to_vec(), + ip: ip.to_vec(), + port, + }, + )) +} + +fn parse_peer(i: &[u8]) -> IResult<&[u8], Peer> { + let (i, ip) = if i.len() < 18 { + take(4usize)(i) + } else { + take(16usize)(i) + }?; + let (i, port) = be_u16(i)?; + Ok(( + i, + Peer { + ip: ip.to_vec(), + port, + }, + )) +} + +impl FromBencode for BitTorrentDHTRequest { + // Try to parse with a `max_depth` of one. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTRequest { // encoded as dictionary (+1) + // id: String, + // target: Option<String>, + // info_hash: Option<String>, + // token: Option<String>, + // implied_port: Option<u8>, + // port: Option<u16>, + // } + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result<Self, Error> + where + Self: Sized, + { + let mut id = None; + let mut target = None; + let mut info_hash = None; + let mut token = None; + let mut implied_port = None; + let mut port = None; + + let mut dict_dec = object.try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"id", value) => { + id = value.try_into_bytes().context("id").map(Some)?; + } + (b"target", value) => { + target = value + .try_into_bytes() + .context("target") + .map(|v| Some(v.to_vec()))?; + } + (b"info_hash", value) => { + info_hash = value + .try_into_bytes() + .context("info_hash") + .map(|v| Some(v.to_vec()))?; + } + (b"token", value) => { + token = value + .try_into_bytes() + .context("token") + .map(|v| Some(v.to_vec()))?; + } + (b"implied_port", value) => { + implied_port = u8::decode_bencode_object(value) + .context("implied_port") + .map(Some)? + } + (b"port", value) => { + port = u16::decode_bencode_object(value) + .context("port") + .map(Some)? + } + (_unknown_field, _) => {} + } + } + + let id = id.ok_or_else(|| Error::missing_field("id"))?; + + Ok(BitTorrentDHTRequest { + id: id.to_vec(), + target, + info_hash, + token, + implied_port, + port, + }) + } +} + +impl FromBencode for BitTorrentDHTResponse { + // Try to parse with a `max_depth` of two. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTResponse { // encoded as dictionary (+1) + // id: String, + // nodes: Option<String>, + // values: Option<Vec<String>>, // if present, encoded as list (+1) + // token: Option<String>, + // } + const EXPECTED_RECURSION_DEPTH: usize = 2; + + fn decode_bencode_object(object: Object) -> Result<Self, Error> + where + Self: Sized, + { + let mut id = None; + let mut nodes = None; + let mut nodes6 = None; + let mut values = vec![]; + let mut token = None; + + let mut dict_dec = object.try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"id", value) => { + id = value.try_into_bytes().context("id").map(Some)?; + } + (b"nodes", value) => { + let (_, decoded_nodes) = + nom7::multi::many0(parse_node)(value.try_into_bytes().context("nodes")?) + .map_err(|_| Error::malformed_content("nodes.node"))?; + if !decoded_nodes.is_empty() { + nodes = Some(decoded_nodes); + } + } + (b"nodes6", value) => { + let (_, decoded_nodes) = + nom7::multi::many0(parse_node6)(value.try_into_bytes().context("nodes6")?) + .map_err(|_| Error::malformed_content("nodes6.nodes6"))?; + if !decoded_nodes.is_empty() { + nodes6 = Some(decoded_nodes); + } + } + (b"values", value) => { + if let Object::List(mut list) = value { + while let Some(entry) = list.next_object()? { + let (_, peer) = + parse_peer(entry.try_into_bytes().context("values.entry")?) + .map_err(|_| Error::malformed_content("values.entry.peer"))?; + values.push(peer); + } + } + } + (b"token", value) => { + token = value + .try_into_bytes() + .context("token") + .map(|v| Some(v.to_vec()))?; + } + (_unknown_field, _) => {} + } + } + + let id = id.ok_or_else(|| Error::missing_field("id"))?; + + Ok(BitTorrentDHTResponse { + id: id.to_vec(), + nodes, + nodes6, + values: if values.is_empty() { + None + } else { + Some(values) + }, + token, + }) + } +} + +impl FromBencode for BitTorrentDHTError { + // Try to parse with a `max_depth` of one. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // struct BitTorrentDHTError { // encoded as dictionary (+1) + // num: u16, + // msg: String, + // } + const EXPECTED_RECURSION_DEPTH: usize = 1; + + fn decode_bencode_object(object: Object) -> Result<Self, Error> + where + Self: Sized, + { + let mut num = None; + let mut msg = None; + + let mut list_dec = object.try_into_list()?; + + while let Some(object) = list_dec.next_object()? { + match object { + Object::Integer(_) => { + num = u16::decode_bencode_object(object) + .context("num") + .map(Some)?; + } + Object::Bytes(_) => { + msg = String::decode_bencode_object(object) + .context("msg") + .map(Some)?; + } + _ => {} + } + } + + let num = num.ok_or_else(|| Error::missing_field("num"))?; + let msg = msg.ok_or_else(|| Error::missing_field("msg"))?; + + Ok(BitTorrentDHTError { num, msg }) + } +} + +pub fn parse_bittorrent_dht_packet( + bytes: &[u8], tx: &mut BitTorrentDHTTransaction, +) -> Result<(), Error> { + // Try to parse with a `max_depth` of three. + // + // The required max depth of a data structure is calculated as follows: + // - every potential nesting level encoded as bencode dictionary or + // list count as +1, + // - everything else is ignored. + // + // - Outer packet is a dictionary (+1) + // - Max depth of child within dictionary is a BitTorrentDHTResponse (+2) + let mut decoder = Decoder::new(bytes).with_max_depth(3); + let object = decoder.next_object()?; + + let mut packet_type = None; + let mut query_type = None; + let mut query_arguments = None; + let mut response = None; + let mut error = None; + let mut transaction_id = None; + let mut client_version = None; + + let mut dict_dec = object + .ok_or_else(|| Error::unexpected_token("Dict", "EOF"))? + .try_into_dictionary()?; + + while let Some(pair) = dict_dec.next_pair()? { + match pair { + (b"y", value) => { + // q (query) vs r (response) vs e (error) + packet_type = String::decode_bencode_object(value) + .context("packet_type") + .map(Some)?; + } + (b"q", value) => { + // query type found + query_type = String::decode_bencode_object(value) + .context("query_type") + .map(Some)?; + } + (b"a", value) => { + // query arguments found + query_arguments = BitTorrentDHTRequest::decode_bencode_object(value) + .context("query_arguments") + .map(Some)?; + } + (b"r", value) => { + // response found + response = BitTorrentDHTResponse::decode_bencode_object(value) + .context("response") + .map(Some)?; + } + (b"e", value) => { + // error found + error = BitTorrentDHTError::decode_bencode_object(value) + .context("error") + .map(Some)?; + } + (b"t", value) => { + // transaction id found + transaction_id = value.try_into_bytes().context("transaction_id").map(Some)?; + } + (b"v", value) => { + // client version string found + client_version = value + .try_into_bytes() + .context("client_version") + .map(|v| Some(v.to_vec()))?; + } + (_unknown_field, _) => {} + } + } + + if let Some(t) = packet_type { + match t.as_str() { + "q" => { + tx.request_type = + Some(query_type.ok_or_else(|| Error::missing_field("query_type"))?); + tx.request = + Some(query_arguments.ok_or_else(|| Error::missing_field("query_arguments"))?); + } + "r" => { + tx.response = Some(response.ok_or_else(|| Error::missing_field("response"))?); + } + "e" => { + tx.error = Some(error.ok_or_else(|| Error::missing_field("error"))?); + } + v => { + return Err(Error::unexpected_token("packet_type q, r, or e", v)); + } + } + } else { + return Err(Error::missing_field("packet_type")); + } + + tx.transaction_id = transaction_id + .ok_or_else(|| Error::missing_field("transaction_id"))? + .to_vec(); + // Client version string is an optional field + tx.client_version = client_version; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::core::Direction; + use test_case::test_case; + + #[test_case( + b"d2:id20:abcdefghij0123456789e", + BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None } ; + "test request from bencode 2")] + #[test_case( + b"d2:id20:abcdefghij01234567899:info_hash20:mnopqrstuvwxyz123456e", + BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: Some(b"mnopqrstuvwxyz123456".to_vec()), port: None, token: None, target: None } ; + "test request from bencode 4")] + fn test_request_from_bencode(encoded: &[u8], expected: BitTorrentDHTRequest) { + let decoded = BitTorrentDHTRequest::from_bencode(encoded).unwrap(); + assert_eq!(expected, decoded); + } + + #[test_case( + b"d12:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: missing field: id" ; + "test request from bencode err 1")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti9999e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: malformed content discovered in implied_port" ; + "test request from bencode err 2")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti-1e9:info_hash20:mnopqrstuvwxyz1234564:porti6881e5:token8:aoeusnthe", + "Error: malformed content discovered in implied_port" ; + "test request from bencode err 3")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti9999999e5:token8:aoeusnthe", + "Error: malformed content discovered in port" ; + "test request from bencode err 4")] + #[test_case( + b"d2:id20:abcdefghij012345678912:implied_porti1e9:info_hash20:mnopqrstuvwxyz1234564:porti-1e5:token8:aoeusnthe", + "Error: malformed content discovered in port" ; + "test request from bencode err 5")] + #[test_case( + b"i123e", + "Error: discovered Dict but expected Num" ; + "test request from bencode err 6")] + fn test_request_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTRequest::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"d5:token8:aoeusnth6:valuesl6:axje.u6:idhtnmee", + "Error: missing field: id" ; + "test response from bencode err 1")] + #[test_case( + b"i123e", + "Error: discovered Dict but expected Num" ; + "test response from bencode err 2")] + fn test_response_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTResponse::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"li201e23:A Generic Error Ocurrede", + BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() } ; + "test error from bencode 1")] + #[test_case( + b"li202e12:Server Errore", + BitTorrentDHTError { num: 202u16, msg: "Server Error".to_string() } ; + "test error from bencode 2")] + #[test_case( + b"li203e14:Protocol Errore", + BitTorrentDHTError { num: 203u16, msg: "Protocol Error".to_string() } ; + "test error from bencode 3")] + #[test_case( + b"li204e14:Method Unknowne", + BitTorrentDHTError { num: 204u16, msg: "Method Unknown".to_string() } ; + "test error from bencode 4")] + fn test_error_from_bencode(encoded: &[u8], expected: BitTorrentDHTError) { + let decoded = BitTorrentDHTError::from_bencode(encoded).unwrap(); + assert_eq!(expected, decoded); + } + + #[test_case( + b"l23:A Generic Error Ocurrede", + "Error: missing field: num" ; + "test error from bencode err 1")] + #[test_case( + b"li201ee", + "Error: missing field: msg" ; + "test error from bencode err 2")] + #[test_case( + b"li999999ee", + "Error: malformed content discovered in num" ; + "test error from bencode err 3")] + #[test_case( + b"li-1ee", + "Error: malformed content discovered in num" ; + "test error from bencode err 4")] + #[test_case( + b"i123e", + "Error: discovered List but expected Num" ; + "test error from bencode err 5")] + fn test_error_from_bencode_err(encoded: &[u8], expected_error: &str) { + let err = BitTorrentDHTError::from_bencode(encoded).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:v4:UT011:y1:qe", + Some("ping".to_string()), + Some(BitTorrentDHTRequest { id: b"abcdefghij0123456789".to_vec(), implied_port: None, info_hash: None, port: None, token: None, target: None }), + None, + None, + b"aa".to_vec(), + Some(b"UT01".to_vec()) ; + "test parse bittorrent dht packet 1" + )] + #[test_case( + b"d1:eli201e23:A Generic Error Ocurrede1:t2:aa1:v4:UT011:y1:ee", + None, + None, + None, + Some(BitTorrentDHTError { num: 201u16, msg: "A Generic Error Ocurred".to_string() }), + b"aa".to_vec(), + Some(b"UT01".to_vec()) ; + "test parse bittorrent dht packet 3" + )] + fn test_parse_bittorrent_dht_packet( + encoded: &[u8], request_type: Option<String>, + expected_request: Option<BitTorrentDHTRequest>, + expected_response: Option<BitTorrentDHTResponse>, + expected_error: Option<BitTorrentDHTError>, expected_transaction_id: Vec<u8>, + expected_client_version: Option<Vec<u8>>, + ) { + let mut tx = BitTorrentDHTTransaction::new(Direction::ToServer); + parse_bittorrent_dht_packet(encoded, &mut tx).unwrap(); + assert_eq!(request_type, tx.request_type); + assert_eq!(expected_request, tx.request); + assert_eq!(expected_response, tx.response); + assert_eq!(expected_error, tx.error); + assert_eq!(expected_transaction_id, tx.transaction_id); + assert_eq!(expected_client_version, tx.client_version); + } + + #[test_case( + b"", + "Error: discovered Dict but expected EOF" ; + "test parse bittorrent dht packet err 1" + )] + #[test_case( + b"li2123ei321ee", + "Error: discovered Dict but expected List" ; + "test parse bittorrent dht packet err 2" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aae", + "Error: missing field: packet_type" ; + "test parse bittorrent dht packet err 3" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:t2:aa1:y1:Fe", + "Error: discovered packet_type q, r, or e but expected F" ; + "test parse bittorrent dht packet err 4" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:t2:aa1:y1:qe", + "Error: missing field: query_type" ; + "test parse bittorrent dht packet err 5" + )] + #[test_case( + b"d1:q4:ping1:t2:aa1:y1:qe", + "Error: missing field: query_arguments" ; + "test parse bittorrent dht packet err 6" + )] + #[test_case( + b"d1:t2:aa1:y1:re", + "Error: missing field: response" ; + "test parse bittorrent dht packet err 7" + )] + #[test_case( + b"d1:t2:aa1:y1:ee", + "Error: missing field: error" ; + "test parse bittorrent dht packet err 8" + )] + #[test_case( + b"d1:ade1:q4:ping1:t2:aa1:y1:qe", + "Error: missing field: id in query_arguments" ; + "test parse bittorrent dht packet err 9" + )] + #[test_case( + b"d1:ad2:id20:abcdefghij0123456789e1:q4:ping1:y1:qe", + "Error: missing field: transaction_id" ; + "test parse bittorrent dht packet err 10" + )] + fn test_parse_bittorrent_dht_packet_err(encoded: &[u8], expected_error: &str) { + let mut tx = BitTorrentDHTTransaction::new(Direction::ToServer); + let err = parse_bittorrent_dht_packet(encoded, &mut tx).unwrap_err(); + assert_eq!(expected_error, err.to_string()); + } + + #[test] + fn test_parse_node() { + let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01"; + let (_rem, node) = parse_node(bytes).unwrap(); + assert_eq!(node.id, b"aaaaaaaaaaaaaaaaaaaa"); + assert_eq!(node.ip, b"\x00\x00\x00\x00"); + assert_eq!(node.port, 1); + + // Short one byte. + let bytes = b"aaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01"; + assert!(parse_node(bytes).is_err()); + + // Has remaining bytes. + let bytes = b"aaaaaaaaaaaaaaaaaaaa\x00\x00\x00\x00\x00\x01bb"; + let (rem, _node) = parse_node(bytes).unwrap(); + assert_eq!(rem, b"bb"); + } +} diff --git a/rust/src/common.rs b/rust/src/common.rs new file mode 100644 index 0000000..1d10bbe --- /dev/null +++ b/rust/src/common.rs @@ -0,0 +1,154 @@ +//! Utility library module for commonly used strings, hexadecimals and other elements. + +use super::build_slice; +use crate::jsonbuilder::HEX; +use std::ffi::CString; +use std::os::raw::c_char; + +pub mod nom7 { + use nom7::bytes::streaming::{tag, take_until}; + use nom7::error::{Error, ParseError}; + use nom7::ErrorConvert; + use nom7::IResult; + + /// Reimplementation of `take_until_and_consume` for nom 7 + /// + /// `take_until` does not consume the matched tag, and + /// `take_until_and_consume` was removed in nom 7. This function + /// provides an implementation (specialized for `&[u8]`). + pub fn take_until_and_consume<'a, E: ParseError<&'a [u8]>>( + t: &'a [u8], + ) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], &'a [u8], E> { + move |i: &'a [u8]| { + let (i, res) = take_until(t)(i)?; + let (i, _) = tag(t)(i)?; + Ok((i, res)) + } + } + + /// Specialized version of the nom 7 `bits` combinator + /// + /// The `bits combinator has trouble inferring the transient error type + /// used by the tuple parser, because the function is generic and any + /// error type would be valid. + /// Use an explicit error type (as described in + /// https://docs.rs/nom/7.1.0/nom/bits/fn.bits.html) to solve this problem, and + /// specialize this function for `&[u8]`. + pub fn bits<'a, O, E, P>(parser: P) -> impl FnMut(&'a [u8]) -> IResult<&'a [u8], O, E> + where + E: ParseError<&'a [u8]>, + Error<(&'a [u8], usize)>: ErrorConvert<E>, + P: FnMut((&'a [u8], usize)) -> IResult<(&'a [u8], usize), O, Error<(&'a [u8], usize)>>, + { + // use full path to disambiguate nom `bits` from this current function name + nom7::bits::bits(parser) + } +} + +#[cfg(not(feature = "debug-validate"))] +#[macro_export] +macro_rules! debug_validate_bug_on ( + ($item:expr) => {}; +); + +#[cfg(feature = "debug-validate")] +#[macro_export] +macro_rules! debug_validate_bug_on ( + ($item:expr) => { + if $item { + panic!("Condition check failed"); + } + }; +); + +#[cfg(not(feature = "debug-validate"))] +#[macro_export] +macro_rules! debug_validate_fail ( + ($msg:expr) => {}; +); + +#[cfg(feature = "debug-validate")] +#[macro_export] +macro_rules! debug_validate_fail ( + ($msg:expr) => { + // Wrap in a conditional to prevent unreachable code warning in caller. + if true { + panic!($msg); + } + }; +); + +/// Convert a String to C-compatible string +/// +/// This function will consume the provided data and use the underlying bytes to construct a new +/// string, ensuring that there is a trailing 0 byte. This trailing 0 byte will be appended by this +/// function; the provided data should *not* contain any 0 bytes in it. +/// +/// Returns a valid pointer, or NULL +pub fn rust_string_to_c(s: String) -> *mut c_char { + CString::new(s) + .map(|c_str| c_str.into_raw()) + .unwrap_or(std::ptr::null_mut()) +} + +/// Free a CString allocated by Rust (for ex. using `rust_string_to_c`) +/// +/// # Safety +/// +/// s must be allocated by rust, using `CString::new` +#[no_mangle] +pub unsafe extern "C" fn rs_cstring_free(s: *mut c_char) { + if s.is_null() { + return; + } + drop(CString::from_raw(s)); +} + +/// Convert an u8-array of data into a hexadecimal representation +pub fn to_hex(input: &[u8]) -> String { + return input + .iter() + .flat_map(|b| { + vec![ + char::from(HEX[(b >> 4) as usize]), + char::from(HEX[(b & 0xf) as usize]), + ] + }) + .collect(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_to_hex( + output: *mut u8, out_len: usize, input: *const u8, in_len: usize, +) { + if out_len < 2 * in_len + 1 { + return; + } + let islice = build_slice!(input, in_len); + let oslice = std::slice::from_raw_parts_mut(output, 2 * in_len + 1); + // only used from C + for i in 0..islice.len() { + oslice[2 * i] = HEX[(islice[i] >> 4) as usize]; + oslice[2 * i + 1] = HEX[(islice[i] & 0xf) as usize]; + } + oslice[2 * islice.len()] = 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_to_hex_sep( + output: *mut u8, out_len: usize, sep: u8, input: *const u8, in_len: usize, +) { + if out_len < 3 * in_len { + return; + } + let islice = build_slice!(input, in_len); + let oslice = std::slice::from_raw_parts_mut(output, 3 * in_len); + // only used from C + for i in 0..islice.len() { + oslice[3 * i] = HEX[(islice[i] >> 4) as usize]; + oslice[3 * i + 1] = HEX[(islice[i] & 0xf) as usize]; + oslice[3 * i + 2] = sep; + } + // overwrites last separator with final null char + oslice[3 * islice.len() - 1] = 0; +} diff --git a/rust/src/conf.rs b/rust/src/conf.rs new file mode 100644 index 0000000..50acf6c --- /dev/null +++ b/rust/src/conf.rs @@ -0,0 +1,322 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Module for retrieving configuration details. + +use std::os::raw::c_char; +use std::os::raw::c_void; +use std::os::raw::c_int; +use std::ffi::{CString, CStr}; +use std::ptr; +use std::str; +use nom7::{ + character::complete::{multispace0, not_line_ending}, + sequence::{preceded, tuple}, + number::complete::double, + combinator::verify, + IResult, +}; + +extern { + fn ConfGet(key: *const c_char, res: *mut *const c_char) -> i8; + fn ConfGetChildValue(conf: *const c_void, key: *const c_char, + vptr: *mut *const c_char) -> i8; + fn ConfGetChildValueBool(conf: *const c_void, key: *const c_char, + vptr: *mut c_int) -> i8; + fn ConfGetNode(key: *const c_char) -> *const c_void; +} + +pub fn conf_get_node(key: &str) -> Option<ConfNode> { + let key = if let Ok(key) = CString::new(key) { + key + } else { + return None; + }; + + let node = unsafe { ConfGetNode(key.as_ptr()) }; + if node.is_null() { + None + } else { + Some(ConfNode::wrap(node)) + } +} + +// Return the string value of a configuration value. +pub fn conf_get(key: &str) -> Option<&str> { + let mut vptr: *const c_char = ptr::null_mut(); + + unsafe { + let s = CString::new(key).unwrap(); + if ConfGet(s.as_ptr(), &mut vptr) != 1 { + SCLogDebug!("Failed to find value for key {}", key); + return None; + } + } + + if vptr.is_null() { + return None; + } + + let value = str::from_utf8(unsafe{ + CStr::from_ptr(vptr).to_bytes() + }).unwrap(); + + return Some(value); +} + +// Return the value of key as a boolean. A value that is not set is +// the same as having it set to false. +pub fn conf_get_bool(key: &str) -> bool { + if let Some(val) = conf_get(key) { + match val { + "1" | "yes" | "true" | "on" => { + return true; + }, + _ => {}, + } + } + + return false; +} + +/// Wrap a Suricata ConfNode and expose some of its methods with a +/// Rust friendly interface. +pub struct ConfNode { + pub conf: *const c_void, +} + +impl ConfNode { + + pub fn wrap(conf: *const c_void) -> Self { + return Self { conf } + } + + pub fn get_child_value(&self, key: &str) -> Option<&str> { + let mut vptr: *const c_char = ptr::null_mut(); + + unsafe { + let s = CString::new(key).unwrap(); + if ConfGetChildValue(self.conf, + s.as_ptr(), + &mut vptr) != 1 { + return None; + } + } + + if vptr.is_null() { + return None; + } + + let value = str::from_utf8(unsafe{ + CStr::from_ptr(vptr).to_bytes() + }).unwrap(); + + return Some(value); + } + + pub fn get_child_bool(&self, key: &str) -> bool { + let mut vptr: c_int = 0; + + unsafe { + let s = CString::new(key).unwrap(); + if ConfGetChildValueBool(self.conf, + s.as_ptr(), + &mut vptr) != 1 { + return false; + } + } + + if vptr == 1 { + return true; + } + return false; + } + +} + +const BYTE: u64 = 1; +const KILOBYTE: u64 = 1024; +const MEGABYTE: u64 = 1_048_576; +const GIGABYTE: u64 = 1_073_741_824; + +/// Helper function to retrieve memory unit from a string slice +/// +/// Return value: u64 +/// +/// # Arguments +/// +/// * `unit` - A string slice possibly containing memory unit +fn get_memunit(unit: &str) -> u64 { + let unit = &unit.to_lowercase()[..]; + match unit { + "b" => { BYTE } + "kb" => { KILOBYTE } + "mb" => { MEGABYTE } + "gb" => { GIGABYTE } + _ => { 0 } + } +} + +/// Parses memory units from human readable form to machine readable +/// +/// Return value: +/// Result => Ok(u64) +/// => Err(error string) +/// +/// # Arguments +/// +/// * `arg` - A string slice that holds the value parsed from the config +pub fn get_memval(arg: &str) -> Result<u64, &'static str> { + let arg = arg.trim(); + let val: f64; + let mut unit: &str; + let mut parser = tuple((preceded(multispace0, double), + preceded(multispace0, verify(not_line_ending, |c: &str| c.len() < 3)))); + let r: IResult<&str, (f64, &str)> = parser(arg); + if let Ok(r) = r { + val = (r.1).0; + unit = (r.1).1; + } else { + return Err("Error parsing the memory value"); + } + if unit.is_empty() { + unit = "B"; + } + let unit = get_memunit(unit); + if unit == 0 { + return Err("Invalid memory unit"); + } + let res = val * unit as f64; + Ok(res as u64) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_memval_nospace() { + let s = "10"; + let res = 10 ; + assert_eq!(Ok(10), get_memval(s)); + + let s = "10kb"; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = "10Kb"; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = "10KB"; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = "10mb"; + assert_eq!(Ok(res * MEGABYTE), get_memval(s)); + + let s = "10gb"; + assert_eq!(Ok(res * GIGABYTE), get_memval(s)); + } + + #[test] + fn test_memval_space_start() { + let s = " 10"; + let res = 10 ; + assert_eq!(Ok(res), get_memval(s)); + + let s = " 10Kb"; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = " 10mb"; + assert_eq!(Ok(res * MEGABYTE), get_memval(s)); + + let s = " 10Gb"; + assert_eq!(Ok(res * GIGABYTE), get_memval(s)); + + let s = " 30b"; + assert_eq!(Ok(30), get_memval(s)); + } + + #[test] + fn test_memval_space_end() { + let s = " 10 "; + let res = 10 ; + assert_eq!(Ok(res), get_memval(s)); + + let s = "10Kb "; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = "10mb "; + assert_eq!(Ok(res * MEGABYTE), get_memval(s)); + + let s = " 10Gb "; + assert_eq!(Ok(res * GIGABYTE), get_memval(s)); + + let s = " 30b "; + assert_eq!(Ok(30), get_memval(s)); + } + + #[test] + fn test_memval_space_in_bw() { + let s = " 10 "; + let res = 10 ; + assert_eq!(Ok(res), get_memval(s)); + + let s = "10 Kb "; + assert_eq!(Ok(res * KILOBYTE), get_memval(s)); + + let s = "10 mb"; + assert_eq!(Ok(res * MEGABYTE), get_memval(s)); + + let s = " 10 Gb "; + assert_eq!(Ok(res * GIGABYTE), get_memval(s)); + + let s = "30 b"; + assert_eq!(Ok(30), get_memval(s)); + } + + #[test] + fn test_memval_float_val() { + let s = " 10.5 "; + assert_eq!(Ok(10), get_memval(s)); + + let s = "10.8Kb "; + assert_eq!(Ok((10.8 * KILOBYTE as f64) as u64), get_memval(s)); + + let s = "10.4 mb "; + assert_eq!(Ok((10.4 * MEGABYTE as f64) as u64), get_memval(s)); + + let s = " 10.5Gb "; + assert_eq!(Ok((10.5 * GIGABYTE as f64) as u64), get_memval(s)); + + let s = " 30.0 b "; + assert_eq!(Ok(30), get_memval(s)); + } + + #[test] + fn test_memval_erroneous_val() { + let s = "5eb"; + assert!(get_memval(s).is_err()); + + let s = "5 1kb"; + assert!(get_memval(s).is_err()); + + let s = "61k b"; + assert!(get_memval(s).is_err()); + + let s = "8 8 k b"; + assert!(get_memval(s).is_err()); + } +} diff --git a/rust/src/core.rs b/rust/src/core.rs new file mode 100644 index 0000000..abb27ea --- /dev/null +++ b/rust/src/core.rs @@ -0,0 +1,345 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! This module exposes items from the core "C" code to Rust. + +use std; +use crate::filecontainer::*; +use crate::debug_validate_fail; + +/// Opaque C types. +pub enum DetectEngineState {} +pub enum AppLayerDecoderEvents {} + +#[repr(C)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum AppLayerEventType { + APP_LAYER_EVENT_TYPE_TRANSACTION = 1, + APP_LAYER_EVENT_TYPE_PACKET = 2, +} + +pub const STREAM_START: u8 = 0x01; +pub const STREAM_EOF: u8 = 0x02; +pub const STREAM_TOSERVER: u8 = 0x04; +pub const STREAM_TOCLIENT: u8 = 0x08; +pub const STREAM_GAP: u8 = 0x10; +pub const STREAM_DEPTH: u8 = 0x20; +pub const STREAM_MIDSTREAM:u8 = 0x40; +pub const DIR_BOTH: u8 = 0b0000_1100; +const DIR_TOSERVER: u8 = 0b0000_0100; +const DIR_TOCLIENT: u8 = 0b0000_1000; + +#[repr(C)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum Direction { + ToServer = 0x04, + ToClient = 0x08, +} + +impl Direction { + /// Return true if the direction is to server. + pub fn is_to_server(&self) -> bool { + matches!(self, Self::ToServer) + } + + /// Return true if the direction is to client. + pub fn is_to_client(&self) -> bool { + matches!(self, Self::ToClient) + } +} + +impl Default for Direction { + fn default() -> Self { Direction::ToServer } +} + +impl std::fmt::Display for Direction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::ToServer => write!(f, "toserver"), + Self::ToClient => write!(f, "toclient"), + } + } +} + +impl From<u8> for Direction { + fn from(d: u8) -> Self { + if d & (DIR_TOSERVER | DIR_TOCLIENT) == (DIR_TOSERVER | DIR_TOCLIENT) { + debug_validate_fail!("Both directions are set"); + Direction::ToServer + } else if d & DIR_TOSERVER != 0 { + Direction::ToServer + } else if d & DIR_TOCLIENT != 0 { + Direction::ToClient + } else { + debug_validate_fail!("Unknown direction!!"); + Direction::ToServer + } + } +} + +impl From<Direction> for u8 { + fn from(d: Direction) -> u8 { + d as u8 + } +} + +// Application layer protocol identifiers (app-layer-protos.h) +pub type AppProto = u16; + +pub const ALPROTO_UNKNOWN : AppProto = 0; +pub static mut ALPROTO_FAILED : AppProto = 0; // updated during init + +pub const IPPROTO_TCP : u8 = 6; +pub const IPPROTO_UDP : u8 = 17; + +/* +macro_rules!BIT_U8 { + ($x:expr) => (1 << $x); +} +*/ +macro_rules!BIT_U16 { + ($x:expr) => (1 << $x); +} + +macro_rules!BIT_U32 { + ($x:expr) => (1 << $x); +} + +macro_rules!BIT_U64 { + ($x:expr) => (1 << $x); +} + +// Flow flags +pub const FLOW_DIR_REVERSED: u32 = BIT_U32!(26); + +// Defined in app-layer-protos.h +extern { + pub fn StringToAppProto(proto_name: *const u8) -> AppProto; +} + +// +// Function types for calls into C. +// + +#[allow(non_snake_case)] +pub type SCLogMessageFunc = + extern "C" fn(level: std::os::raw::c_int, + filename: *const std::os::raw::c_char, + line: std::os::raw::c_uint, + function: *const std::os::raw::c_char, + subsystem: *const std::os::raw::c_char, + message: *const std::os::raw::c_char) -> std::os::raw::c_int; + +pub type DetectEngineStateFreeFunc = + extern "C" fn(state: *mut DetectEngineState); + +pub type AppLayerParserTriggerRawStreamReassemblyFunc = + extern "C" fn (flow: *const Flow, direction: i32); +pub type AppLayerDecoderEventsSetEventRawFunc = + extern "C" fn (events: *mut *mut AppLayerDecoderEvents, + event: u8); + +pub type AppLayerDecoderEventsFreeEventsFunc = + extern "C" fn (events: *mut *mut AppLayerDecoderEvents); + +pub enum StreamingBufferConfig {} + +// Opaque flow type (defined in C) +pub enum HttpRangeContainerBlock {} + +pub type SCHttpRangeFreeBlock = extern "C" fn ( + c: *mut HttpRangeContainerBlock); +pub type SCHTPFileCloseHandleRange = extern "C" fn ( + sbcfg: &StreamingBufferConfig, + fc: *mut FileContainer, + flags: u16, + c: *mut HttpRangeContainerBlock, + data: *const u8, + data_len: u32) -> bool; +pub type SCFileOpenFileWithId = extern "C" fn ( + file_container: &FileContainer, + sbcfg: &StreamingBufferConfig, + track_id: u32, + name: *const u8, name_len: u16, + data: *const u8, data_len: u32, + flags: u16) -> i32; +pub type SCFileCloseFileById = extern "C" fn ( + file_container: &FileContainer, + sbcfg: &StreamingBufferConfig, + track_id: u32, + data: *const u8, data_len: u32, + flags: u16) -> i32; +pub type SCFileAppendDataById = extern "C" fn ( + file_container: &FileContainer, + sbcfg: &StreamingBufferConfig, + track_id: u32, + data: *const u8, data_len: u32) -> i32; +pub type SCFileAppendGAPById = extern "C" fn ( + file_container: &FileContainer, + sbcfg: &StreamingBufferConfig, + track_id: u32, + data: *const u8, data_len: u32) -> i32; +pub type SCFileContainerRecycle = extern "C" fn ( + file_container: &FileContainer, + sbcfg: &StreamingBufferConfig); + +// A Suricata context that is passed in from C. This is alternative to +// using functions from Suricata directly, so they can be wrapped so +// Rust unit tests will still compile when they are not linked +// directly to the real function. +// +// This might add a little too much complexity to keep pure Rust test +// cases working. +#[allow(non_snake_case)] +#[repr(C)] +pub struct SuricataContext { + pub SCLogMessage: SCLogMessageFunc, + DetectEngineStateFree: DetectEngineStateFreeFunc, + AppLayerDecoderEventsSetEventRaw: AppLayerDecoderEventsSetEventRawFunc, + AppLayerDecoderEventsFreeEvents: AppLayerDecoderEventsFreeEventsFunc, + pub AppLayerParserTriggerRawStreamReassembly: AppLayerParserTriggerRawStreamReassemblyFunc, + + pub HttpRangeFreeBlock: SCHttpRangeFreeBlock, + pub HTPFileCloseHandleRange: SCHTPFileCloseHandleRange, + + pub FileOpenFile: SCFileOpenFileWithId, + pub FileCloseFile: SCFileCloseFileById, + pub FileAppendData: SCFileAppendDataById, + pub FileAppendGAP: SCFileAppendGAPById, + pub FileContainerRecycle: SCFileContainerRecycle, + + pub AppLayerRegisterParser: extern fn(parser: *const crate::applayer::RustParser, alproto: AppProto) -> std::os::raw::c_int, +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct SuricataFileContext { + pub files_sbcfg: &'static StreamingBufferConfig, +} + +extern { + pub fn SCGetContext() -> &'static mut SuricataContext; + pub fn SCLogGetLogLevel() -> i32; +} + +pub static mut SC: Option<&'static SuricataContext> = None; + +pub fn init_ffi(context: &'static SuricataContext) +{ + unsafe { + SC = Some(context); + ALPROTO_FAILED = StringToAppProto("failed\0".as_ptr()); + } +} + +#[no_mangle] +pub extern "C" fn rs_init(context: &'static SuricataContext) +{ + init_ffi(context); +} + +/// DetectEngineStateFree wrapper. +pub fn sc_detect_engine_state_free(state: *mut DetectEngineState) +{ + unsafe { + if let Some(c) = SC { + (c.DetectEngineStateFree)(state); + } + } +} + +/// AppLayerParserTriggerRawStreamReassembly wrapper +pub fn sc_app_layer_parser_trigger_raw_stream_reassembly(flow: *const Flow, direction: i32) { + unsafe { + if let Some(c) = SC { + (c.AppLayerParserTriggerRawStreamReassembly)(flow, direction); + } + } +} + +/// AppLayerDecoderEventsSetEventRaw wrapper. +pub fn sc_app_layer_decoder_events_set_event_raw( + events: *mut *mut AppLayerDecoderEvents, event: u8) +{ + unsafe { + if let Some(c) = SC { + (c.AppLayerDecoderEventsSetEventRaw)(events, event); + } + } +} + +/// AppLayerDecoderEventsFreeEvents wrapper. +pub fn sc_app_layer_decoder_events_free_events( + events: *mut *mut AppLayerDecoderEvents) +{ + unsafe { + if let Some(c) = SC { + (c.AppLayerDecoderEventsFreeEvents)(events); + } + } +} + +/// Opaque flow type (defined in C) +pub enum Flow {} + +// Extern functions operating on Flow. +extern { + pub fn FlowGetLastTimeAsParts(flow: &Flow, secs: *mut u64, usecs: *mut u64); + pub fn FlowGetFlags(flow: &Flow) -> u32; + pub fn FlowGetSourcePort(flow: &Flow) -> u16; + pub fn FlowGetDestinationPort(flow: &Flow) -> u16; +} + +/// Rust implementation of Flow. +impl Flow { + + /// Return the time of the last flow update as a `Duration` + /// since the epoch. + pub fn get_last_time(&mut self) -> std::time::Duration { + unsafe { + let mut secs: u64 = 0; + let mut usecs: u64 = 0; + FlowGetLastTimeAsParts(self, &mut secs, &mut usecs); + std::time::Duration::new(secs, usecs as u32 * 1000) + } + } + + /// Return the flow flags. + pub fn get_flags(&self) -> u32 { + unsafe { FlowGetFlags(self) } + } + + /// Return flow ports + pub fn get_ports(&self) -> (u16, u16) { + unsafe { (FlowGetSourcePort(self), FlowGetDestinationPort(self)) } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_direction() { + assert!(Direction::ToServer.is_to_server()); + assert!(!Direction::ToServer.is_to_client()); + + assert!(Direction::ToClient.is_to_client()); + assert!(!Direction::ToClient.is_to_server()); + } +} diff --git a/rust/src/dcerpc/dcerpc.rs b/rust/src/dcerpc/dcerpc.rs new file mode 100644 index 0000000..759d5c2 --- /dev/null +++ b/rust/src/dcerpc/dcerpc.rs @@ -0,0 +1,2548 @@ +/* Copyright (C) 2020-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::applayer::{self, *}; +use crate::core::{self, *}; +use crate::dcerpc::parser; +use nom7::error::{Error, ErrorKind}; +use nom7::number::Endianness; +use nom7::{Err, IResult, Needed}; +use std; +use std::cmp; +use std::ffi::CString; +use std::collections::VecDeque; +use crate::conf::conf_get; + +// Constant DCERPC UDP Header length +pub const DCERPC_HDR_LEN: u16 = 16; +// FIRST flag set on the packet +pub const DCERPC_UUID_ENTRY_FLAG_FF: u16 = 0x0001; + +// Flag bits in connection-oriented PDU header + +// Value to indicate first fragment +pub const PFC_FIRST_FRAG: u8 = 0x01; +// Value to indicate last fragment +pub const PFC_LAST_FRAG: u8 = 0x02; +// Cancel was pending at sender +pub const PFC_PENDING_CANCEL: u8 = 0x04; +pub const PFC_RESERVED_1: u8 = 0x08; +// supports concurrent multiplexing of a single connection. +pub const PFC_CONC_MPX: u8 = 0x10; +// only meaningful on `fault' packet; if true, guaranteed +// call did not execute. +pub const PFC_DID_NOT_EXECUTE: u8 = 0x20; +// `maybe' call semantics requested +pub const PFC_MAYBE: u8 = 0x40; +// if true, a non-nil object UUID was specified in the handle, and +// is present in the optional object field. If false, the object field +// is omitted. +pub const PFC_OBJECT_UUID: u8 = 0x80; + +// Flag bits in first flag field in connectionless PDU header. +pub const PFCL1_RESERVED_01: u8 = 0x01; // Reserved for use by implementations +pub const PFCL1_LASTFRAG: u8 = 0x02; // If set, the PDU is the last fragment + // of a multi-PDU transmission +pub const PFCL1_FRAG: u8 = 0x04; // If set, the PDU is a fragment + // of a multi-PDU transmission +pub const PFCL1_NOFACK: u8 = 0x08; // If set, the receiver is not requested + // to send a `fack' PDU for the fragment +pub const PFCL1_MAYBE: u8 = 0x10; // If set, the PDU is for a `maybe' request +pub const PFCL1_IDEMPOTENT: u8 = 0x20; // If set, the PDU is for + // an idempotent request +pub const PFCL1_BROADCAST: u8 = 0x40; // If set, the PDU is for + // a broadcast request +pub const PFCL1_RESERVED_80: u8 = 0x80; // Reserved for use by implementations + +// Flag bits in second flag field in connectionless PDU header. +pub const PFCL2_RESERVED_01: u8 = 0x01; // Reserved for use by implementations +pub const PFCL2_CANCEL_PENDING: u8 = 0x02; // Cancel pending at the call end +pub const PFCL2_RESERVED_04: u8 = 0x04; // Reserved for future use +pub const PFCL2_RESERVED_08: u8 = 0x08; // Reserved for future use +pub const PFCL2_RESERVED_10: u8 = 0x10; // Reserved for future use +pub const PFCL2_RESERVED_20: u8 = 0x20; // Reserved for future use +pub const PFCL2_RESERVED_40: u8 = 0x40; // Reserved for future use +pub const PFCL2_RESERVED_80: u8 = 0x80; // Reserved for future use + +pub const REASON_NOT_SPECIFIED: u8 = 0; +pub const TEMPORARY_CONGESTION: u8 = 1; +pub const LOCAL_LIMIT_EXCEEDED: u8 = 2; +pub const CALLED_PADDR_UNKNOWN: u8 = 3; /* not used */ +pub const PROTOCOL_VERSION_NOT_SUPPORTED: u8 = 4; +pub const DEFAULT_CONTEXT_NOT_SUPPORTED: u8 = 5; /* not used */ +pub const USER_DATA_NOT_READABLE: u8 = 6; /* not used */ +pub const NO_PSAP_AVAILABLE: u8 = 7; /* not used */ + +// DCERPC Header packet types +pub const DCERPC_TYPE_REQUEST: u8 = 0; +pub const DCERPC_TYPE_PING: u8 = 1; +pub const DCERPC_TYPE_RESPONSE: u8 = 2; +pub const DCERPC_TYPE_FAULT: u8 = 3; +pub const DCERPC_TYPE_WORKING: u8 = 4; +pub const DCERPC_TYPE_NOCALL: u8 = 5; +pub const DCERPC_TYPE_REJECT: u8 = 6; +pub const DCERPC_TYPE_ACK: u8 = 7; +pub const DCERPC_TYPE_CL_CANCEL: u8 = 8; +pub const DCERPC_TYPE_FACK: u8 = 9; +pub const DCERPC_TYPE_CANCEL_ACK: u8 = 10; +pub const DCERPC_TYPE_BIND: u8 = 11; +pub const DCERPC_TYPE_BINDACK: u8 = 12; +pub const DCERPC_TYPE_BINDNAK: u8 = 13; +pub const DCERPC_TYPE_ALTER_CONTEXT: u8 = 14; +pub const DCERPC_TYPE_ALTER_CONTEXT_RESP: u8 = 15; +pub const DCERPC_TYPE_AUTH3: u8 = 16; +pub const DCERPC_TYPE_SHUTDOWN: u8 = 17; +pub const DCERPC_TYPE_CO_CANCEL: u8 = 18; +pub const DCERPC_TYPE_ORPHANED: u8 = 19; +pub const DCERPC_TYPE_RTS: u8 = 20; +pub const DCERPC_TYPE_UNKNOWN: u8 = 99; + +pub(super) static mut DCERPC_MAX_TX: usize = 1024; + +pub static mut ALPROTO_DCERPC: AppProto = ALPROTO_UNKNOWN; + +pub fn dcerpc_type_string(t: u8) -> String { + match t { + DCERPC_TYPE_REQUEST => "REQUEST", + DCERPC_TYPE_PING => "PING", + DCERPC_TYPE_RESPONSE => "RESPONSE", + DCERPC_TYPE_FAULT => "FAULT", + DCERPC_TYPE_WORKING => "WORKING", + DCERPC_TYPE_NOCALL => "NOCALL", + DCERPC_TYPE_REJECT => "REJECT", + DCERPC_TYPE_ACK => "ACK", + DCERPC_TYPE_CL_CANCEL => "CL_CANCEL", + DCERPC_TYPE_FACK => "FACK", + DCERPC_TYPE_CANCEL_ACK => "CANCEL_ACK", + DCERPC_TYPE_BIND => "BIND", + DCERPC_TYPE_BINDACK => "BINDACK", + DCERPC_TYPE_BINDNAK => "BINDNAK", + DCERPC_TYPE_ALTER_CONTEXT => "ALTER_CONTEXT", + DCERPC_TYPE_ALTER_CONTEXT_RESP => "ALTER_CONTEXT_RESP", + DCERPC_TYPE_AUTH3 => "AUTH3", + DCERPC_TYPE_SHUTDOWN => "SHUTDOWN", + DCERPC_TYPE_CO_CANCEL => "CO_CANCEL", + DCERPC_TYPE_ORPHANED => "ORPHANED", + DCERPC_TYPE_RTS => "RTS", + DCERPC_TYPE_UNKNOWN => "UNKNOWN", + _ => { + return (t).to_string(); + } + } + .to_string() +} + +pub fn get_resp_type_for_req(t: u8) -> u8 { + match t { + DCERPC_TYPE_REQUEST => DCERPC_TYPE_RESPONSE, + DCERPC_TYPE_BIND => DCERPC_TYPE_BINDACK, + DCERPC_TYPE_ALTER_CONTEXT => DCERPC_TYPE_ALTER_CONTEXT_RESP, + _ => DCERPC_TYPE_UNKNOWN, + } +} + +pub fn get_req_type_for_resp(t: u8) -> u8 { + match t { + DCERPC_TYPE_RESPONSE => DCERPC_TYPE_REQUEST, + DCERPC_TYPE_BINDACK => DCERPC_TYPE_BIND, + DCERPC_TYPE_ALTER_CONTEXT_RESP => DCERPC_TYPE_ALTER_CONTEXT, + _ => DCERPC_TYPE_UNKNOWN, + } +} + +#[derive(Default, Debug)] +pub struct DCERPCTransaction { + pub id: u64, // internal transaction ID + pub ctxid: u16, + pub opnum: u16, + pub first_request_seen: u8, + pub call_id: u32, // ID to match any request-response pair + pub frag_cnt_ts: u16, + pub frag_cnt_tc: u16, + pub endianness: u8, + pub stub_data_buffer_ts: Vec<u8>, + pub stub_data_buffer_tc: Vec<u8>, + pub stub_data_buffer_reset_ts: bool, + pub stub_data_buffer_reset_tc: bool, + pub req_done: bool, + pub resp_done: bool, + pub req_lost: bool, + pub resp_lost: bool, + pub req_cmd: u8, + pub resp_cmd: u8, + pub activityuuid: Vec<u8>, + pub seqnum: u32, + pub tx_data: AppLayerTxData, +} + +impl Transaction for DCERPCTransaction { + fn id(&self) -> u64 { + // need +1 to match state.tx_id + self.id + 1 + } +} + +impl DCERPCTransaction { + pub fn new() -> Self { + return Self { + stub_data_buffer_ts: Vec::new(), + stub_data_buffer_tc: Vec::new(), + req_cmd: DCERPC_TYPE_REQUEST, + resp_cmd: DCERPC_TYPE_RESPONSE, + activityuuid: Vec::new(), + tx_data: AppLayerTxData::new(), + ..Default::default() + } + } + + pub fn get_req_ctxid(&self) -> u16 { + self.ctxid + } + + pub fn get_first_req_seen(&self) -> u8 { + self.first_request_seen + } + + pub fn get_req_opnum(&self) -> u16 { + self.opnum + } + + pub fn get_endianness(&self) -> u8 { + self.endianness + } +} + +#[derive(Debug)] +pub struct DCERPCRequest { + pub ctxid: u16, + pub opnum: u16, + pub first_request_seen: u8, +} + +#[derive(Default, Debug, Clone)] +pub struct DCERPCUuidEntry { + pub ctxid: u16, + pub internal_id: u16, + pub result: u16, + pub uuid: Vec<u8>, + pub version: u16, + pub versionminor: u16, + pub flags: u16, +} + +impl DCERPCUuidEntry { + pub fn new() -> Self { + Default::default() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Uuid { + pub time_low: Vec<u8>, + pub time_mid: Vec<u8>, + pub time_hi_and_version: Vec<u8>, + pub clock_seq_hi_and_reserved: u8, + pub clock_seq_low: u8, + pub node: Vec<u8>, +} + +#[derive(Debug)] +pub struct DCERPCHdr { + pub rpc_vers: u8, + pub rpc_vers_minor: u8, + pub hdrtype: u8, + pub pfc_flags: u8, + pub packed_drep: Vec<u8>, + pub frag_length: u16, + pub auth_length: u16, + pub call_id: u32, +} + +#[derive(Debug)] +pub struct DCERPCBind { + pub numctxitems: u8, + pub uuid_list: Vec<DCERPCUuidEntry>, +} + +#[derive(Debug)] +pub struct BindCtxItem { + pub ctxid: u16, + pub uuid: Vec<u8>, + pub version: u16, + pub versionminor: u16, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DCERPCBindAckResult { + pub ack_result: u16, + pub ack_reason: u16, + pub transfer_syntax: Vec<u8>, + pub syntax_version: u32, +} + +#[derive(Debug)] +pub struct DCERPCBindAck { + pub accepted_uuid_list: Vec<DCERPCUuidEntry>, + pub sec_addr_len: u16, + pub numctxitems: u8, + pub ctxitems: Vec<DCERPCBindAckResult>, +} + +#[derive(Default, Debug)] +pub struct DCERPCState { + pub header: Option<DCERPCHdr>, + pub bind: Option<DCERPCBind>, + pub bindack: Option<DCERPCBindAck>, + pub transactions: VecDeque<DCERPCTransaction>, + tx_index_completed: usize, + pub buffer_ts: Vec<u8>, + pub buffer_tc: Vec<u8>, + pub pad: u8, + pub padleft: u16, + pub bytes_consumed: i32, + pub tx_id: u64, + pub query_completed: bool, + pub data_needed_for_dir: Direction, + pub prev_dir: Direction, + pub ts_gap: bool, + pub tc_gap: bool, + pub ts_ssn_gap: bool, + pub tc_ssn_gap: bool, + pub ts_ssn_trunc: bool, /// true if Truncated in this direction + pub tc_ssn_trunc: bool, + pub flow: Option<*const core::Flow>, + state_data: AppLayerStateData, +} + +impl State<DCERPCTransaction> for DCERPCState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&DCERPCTransaction> { + self.transactions.get(index) + } +} + +impl DCERPCState { + pub fn new() -> Self { + return Self { + data_needed_for_dir: Direction::ToServer, + prev_dir: Direction::ToServer, + ..Default::default() + } + } + + fn create_tx(&mut self, call_id: u32) -> DCERPCTransaction { + let mut tx = DCERPCTransaction::new(); + let endianness = self.get_hdr_drep_0() & 0x10; + tx.id = self.tx_id; + tx.call_id = call_id; + tx.endianness = endianness; + self.tx_id += 1; + tx.req_done = self.ts_ssn_trunc; + tx.resp_done = self.tc_ssn_trunc; + if self.transactions.len() > unsafe { DCERPC_MAX_TX } { + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if !tx_old.req_done || !tx_old.resp_done { + tx_old.req_done = true; + tx_old.resp_done = true; + break; + } + } + self.tx_index_completed = index; + } + tx + } + + pub fn free_tx(&mut self, tx_id: u64) { + SCLogDebug!("Freeing TX with ID {} TX.ID {}", tx_id, tx_id+1); + 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; + SCLogDebug!("tx {} progress {}/{}", tx.id, tx.req_done, tx.resp_done); + break; + } + } + if found { + SCLogDebug!("freeing TX with ID {} TX.ID {} at index {} left: {} max id: {}", + tx_id, tx_id+1, index, self.transactions.len(), self.tx_id); + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + fn get_hdr_drep_0(&self) -> u8 { + if let Some(ref hdr) = &self.header { + return hdr.packed_drep[0]; + } + 0 + } + + fn get_endianness(&self) -> Endianness { + let drep_0 = self.get_hdr_drep_0(); + if drep_0 & 0x10 == 0 { + return Endianness::Big; + } + Endianness::Little + } + + fn get_hdr_fraglen(&self) -> Option<u16> { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = self.header { + return Some(hdr.frag_length); + } + // Shouldn't happen + None + } + + fn get_hdr_pfcflags(&self) -> Option<u8> { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = self.header { + return Some(hdr.pfc_flags); + } + // Shouldn't happen + None + } + + pub fn get_hdr_type(&self) -> Option<u8> { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = self.header { + return Some(hdr.hdrtype); + } + // Shouldn't happen + None + } + + pub fn get_hdr_call_id(&self) -> Option<u32> { + debug_validate_bug_on!(self.header.is_none()); + if let Some(ref hdr) = self.header { + return Some(hdr.call_id); + } + // Shouldn't happen + None + } + + pub fn clean_buffer(&mut self, direction: Direction) { + match direction { + Direction::ToServer => { + self.buffer_ts.clear(); + self.ts_gap = false; + } + Direction::ToClient => { + self.buffer_tc.clear(); + self.tc_gap = false; + } + } + self.bytes_consumed = 0; + } + + pub fn extend_buffer(&mut self, buffer: &[u8], direction: Direction) { + match direction { + Direction::ToServer => { + self.buffer_ts.extend_from_slice(buffer); + } + Direction::ToClient => { + self.buffer_tc.extend_from_slice(buffer); + } + } + self.data_needed_for_dir = direction; + } + + pub fn reset_direction(&mut self, direction: Direction) { + if direction == Direction::ToServer { + self.data_needed_for_dir = Direction::ToClient; + } else { + self.data_needed_for_dir = Direction::ToServer; + } + } + + /// Get transaction as per the given transaction ID. Transaction ID with + /// which the lookup is supposed to be done as per the calls from AppLayer + /// parser in C. This requires an internal transaction ID to be maintained. + /// + /// Arguments: + /// * `tx_id`: + /// type: unsigned 32 bit integer + /// description: internal transaction ID to track transactions + /// + /// Return value: + /// Option mutable reference to DCERPCTransaction + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut DCERPCTransaction> { + for tx in &mut self.transactions { + let found = tx.id == tx_id; + if found { + return Some(tx); + } + } + None + } + + /// Find the transaction as per call ID defined in header. If the tx is not + /// found, create one. + /// + /// Arguments: + /// * `call_id`: + /// type: unsigned 32 bit integer + /// description: call_id param derived from TCP Header + /// * `dir`: + /// type: enum Direction + /// description: direction of the flow + /// + /// Return value: + /// Option mutable reference to DCERPCTransaction + pub fn get_tx_by_call_id(&mut self, call_id: u32, dir: Direction) -> Option<&mut DCERPCTransaction> { + let cmd = self.get_hdr_type().unwrap_or(0); + for tx in &mut self.transactions { + let found = tx.call_id == call_id; + if found { + match dir { + Direction::ToServer => { + let resp_cmd = get_resp_type_for_req(cmd); + if resp_cmd != tx.resp_cmd { + continue; + } + } + Direction::ToClient => { + let req_cmd = get_req_type_for_resp(cmd); + if req_cmd != tx.req_cmd { + continue; + } + } + } + return Some(tx); + } + } + None + } + + pub fn parse_data_gap(&mut self, direction: Direction) -> AppLayerResult { + match direction { + Direction::ToServer => { + self.ts_gap = true; + self.ts_ssn_gap = true; + }, + Direction::ToClient => { + self.tc_gap = true; + self.tc_ssn_gap = true; + }, + } + AppLayerResult::ok() + } + + pub fn post_gap_housekeeping(&mut self, dir: Direction) { + SCLogDebug!("ts ssn gap: {:?}, tc ssn gap: {:?}, dir: {:?}", self.ts_ssn_gap, self.tc_ssn_gap, dir); + 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 !tx.req_done { + tx.req_lost = true; + } + tx.req_done = true; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, dir as i32); + } + } + } 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 !tx.req_done { + tx.req_lost = true; + } + if !tx.resp_done { + tx.resp_lost = true; + } + tx.req_done = true; + tx.resp_done = true; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, dir as i32); + } + } + } + } + + pub fn search_dcerpc_record<'a>(&mut self, i: &'a[u8]) -> IResult<&'a[u8], &'a[u8]> { + let mut d = i; + while d.len() >= 2 { + if d[0] == 0x05 && d[1] == 0x00 { + return Ok((&d[2..], d)); + } + d = &d[1..]; + } + Err(Err::Incomplete(Needed::new(2_usize - d.len()))) + } + + /// Makes a call to the nom parser for parsing DCERPC Header. + /// + /// Arguments: + /// * `input`: + /// type: u8 vector slice. + /// description: bytes from the beginning of the buffer. + /// + /// Return value: + /// * Success: Number of bytes successfully parsed. + /// * Failure: -1 in case of Incomplete data or Eof. + /// -2 in case of Error while parsing. + pub fn process_header(&mut self, input: &[u8]) -> i32 { + match parser::parse_dcerpc_header(input) { + Ok((leftover_bytes, header)) => { + if header.rpc_vers != 5 + || (header.rpc_vers_minor != 0 && header.rpc_vers_minor != 1) + { + SCLogDebug!( + "DCERPC Header did not validate. Major version: {:?} Minor version: {:?}", + header.rpc_vers, + header.rpc_vers_minor + ); + return -1; + } + self.header = Some(header); + (input.len() - leftover_bytes.len()) as i32 + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC header"); + -1 + } + Err(Err::Error(Error{code:ErrorKind::Eof, ..})) => { + SCLogDebug!("EoF reached while parsing DCERPC header"); + -1 + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC header"); + -2 + } + } + } + + pub fn handle_bindctxitem(&mut self, input: &[u8], uuid_internal_id: u16) -> i32 { + let endianness = self.get_endianness(); + match parser::parse_bindctx_item(input, endianness) { + Ok((leftover_bytes, ctxitem)) => { + let mut uuidentry = DCERPCUuidEntry::new(); + uuidentry.uuid = ctxitem.uuid; + uuidentry.internal_id = uuid_internal_id; + uuidentry.ctxid = ctxitem.ctxid; + uuidentry.version = ctxitem.version; + uuidentry.versionminor = ctxitem.versionminor; + let pfcflags = self.get_hdr_pfcflags().unwrap_or(0); + // Store the first frag flag in the uuid as pfc_flags will + // be overwritten by new packets + if pfcflags & PFC_FIRST_FRAG > 0 { + uuidentry.flags |= DCERPC_UUID_ENTRY_FLAG_FF; + } + if let Some(ref mut bind) = self.bind { + SCLogDebug!("DCERPC BIND CtxItem: Pushing uuid: {:?}", uuidentry); + bind.uuid_list.push(uuidentry); + } + (input.len() - leftover_bytes.len()) as i32 + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC BIND CTXItem"); + -1 + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC BIND CTXItem"); + -1 + } + } + } + + pub fn process_bind_pdu(&mut self, input: &[u8]) -> i32 { + let mut retval = 0; + let mut idx = 12; // Bytes consumed if parser returns OK would be 12 + match parser::parse_dcerpc_bind(input) { + Ok((leftover_bytes, header)) => { + let numctxitems = header.numctxitems; + self.bind = Some(header); + for i in 0..numctxitems { + retval = self.handle_bindctxitem(&input[idx as usize..], i as u16); + if retval == -1 { + return -1; + } + idx += retval; + } + let call_id = self.get_hdr_call_id().unwrap_or(0); + let mut tx = self.create_tx(call_id); + tx.req_cmd = self.get_hdr_type().unwrap_or(0); + tx.req_done = true; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToServer as i32); + } + tx.frag_cnt_ts = 1; + self.transactions.push_back(tx); + // Bytes parsed with `parse_dcerpc_bind` + (bytes parsed per bindctxitem [44] * number + // of bindctxitems) + (input.len() - leftover_bytes.len()) as i32 + retval * numctxitems as i32 + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC BIND header"); + -1 + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC BIND header"); + -1 + } + } + } + + pub fn process_bindack_pdu(&mut self, input: &[u8]) -> i32 { + match parser::parse_dcerpc_bindack(input) { + Ok((leftover_bytes, mut back)) => { + if let Some(ref mut bind) = self.bind { + for (uuid_internal_id, r) in back.ctxitems.iter().enumerate() { + for uuid in bind.uuid_list.iter_mut() { + if uuid.internal_id == uuid_internal_id as u16 { + uuid.result = r.ack_result; + if uuid.result != 0 { + break; + } + back.accepted_uuid_list.push(uuid.clone()); + SCLogDebug!("DCERPC BINDACK accepted UUID: {:?}", uuid); + } + } + } + self.bindack = Some(back); + } + (input.len() - leftover_bytes.len()) as i32 + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC BINDACK"); + -1 + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC BINDACK"); + -1 + } + } + } + + pub fn handle_stub_data(&mut self, input: &[u8], input_len: usize, dir: Direction) -> u16 { + let retval; + let hdrpfcflags = self.get_hdr_pfcflags().unwrap_or(0); + let padleft = self.padleft; + let call_id = self.get_hdr_call_id().unwrap_or(0); + let hdrtype = self.get_hdr_type(); + let tx; + if let Some(transaction) = self.get_tx_by_call_id(call_id, dir) { + tx = transaction; + } else { + SCLogDebug!("No transaction found matching the call ID: {:?}", call_id); + return 0; + } + + // Update the stub params based on the packet type + match hdrtype { + Some(x) => match x { + DCERPC_TYPE_REQUEST => { + retval = evaluate_stub_params( + input, + input_len, + hdrpfcflags, + padleft, + &mut tx.stub_data_buffer_ts, + &mut tx.stub_data_buffer_reset_ts, + ); + tx.req_done = true; + tx.frag_cnt_ts = 1; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToServer as i32); + } + } + DCERPC_TYPE_RESPONSE => { + retval = evaluate_stub_params( + input, + input_len, + hdrpfcflags, + padleft, + &mut tx.stub_data_buffer_tc, + &mut tx.stub_data_buffer_reset_tc, + ); + tx.resp_done = true; + tx.frag_cnt_tc = 1; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToClient as i32); + } + } + _ => { + SCLogDebug!("Unrecognized packet type"); + return 0; + } + }, + None => { + return 0; + } + } + // Update the remaining fragment length + self.padleft -= retval; + + retval + } + + /// Handles stub data for both request and response. + /// + /// Arguments: + /// * `input`: + /// type: u8 vector slice. + /// description: bytes left *after* parsing header. + /// * `bytes_consumed`: + /// type: 16 bit unsigned integer. + /// description: bytes consumed *after* parsing header. + /// * `dir`: + /// type: enum Direction. + /// description: direction whose stub is supposed to be handled. + /// + /// Return value: + /// * Success: Number of bytes successfully parsed. + /// * Failure: -1 in case fragment length defined by header mismatches the data. + pub fn handle_common_stub(&mut self, input: &[u8], bytes_consumed: usize, dir: Direction) -> i32 { + let fraglen = self.get_hdr_fraglen().unwrap_or(0); + if (fraglen as usize) < bytes_consumed + (DCERPC_HDR_LEN as usize) { + return -1; + } + // Above check makes sure padleft stays in u16 limits + self.padleft = fraglen - DCERPC_HDR_LEN - bytes_consumed as u16; + let mut input_left = input.len() - bytes_consumed; + let mut parsed = bytes_consumed as i32; + while input_left > 0 && parsed < fraglen as i32 { + let retval = self.handle_stub_data(&input[parsed as usize..], input_left, dir); + if retval > 0 && retval as usize <= input_left { + parsed += retval as i32; + input_left -= <u16 as std::convert::Into<usize>>::into(retval); + } else if input_left > 0 { + SCLogDebug!( + "Error parsing DCERPC {} stub data", + if dir == Direction::ToServer { + "request" + } else { + "response" + } + ); + parsed -= input_left as i32; + input_left = 0; + } + } + parsed + } + + pub fn process_request_pdu(&mut self, input: &[u8]) -> i32 { + let endianness = self.get_endianness(); + match parser::parse_dcerpc_request(input, endianness) { + Ok((leftover_input, request)) => { + let call_id = self.get_hdr_call_id().unwrap_or(0); + let hdr_type = self.get_hdr_type().unwrap_or(0); + let mut transaction = self.get_tx_by_call_id(call_id, Direction::ToServer); + match transaction { + Some(ref mut tx) => { + tx.req_cmd = hdr_type; + tx.ctxid = request.ctxid; + tx.opnum = request.opnum; + tx.first_request_seen = request.first_request_seen; + } + None => { + let mut tx = self.create_tx(call_id); + tx.req_cmd = hdr_type; + tx.ctxid = request.ctxid; + tx.opnum = request.opnum; + tx.first_request_seen = request.first_request_seen; + self.transactions.push_back(tx); + } + } + let parsed = self.handle_common_stub( + input, + input.len() - leftover_input.len(), + Direction::ToServer, + ); + parsed + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC REQUEST"); + -1 + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC REQUEST"); + -1 + } + } + } + + pub fn handle_input_data(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { + let mut parsed; + let retval; + let mut cur_i = input; + let input_len = cur_i.len(); + let mut v: Vec<u8>; + // Set any query's completion status to false in the beginning + self.query_completed = false; + + // Skip the record since this means that its in the middle of a known length record + if (self.ts_gap && direction == Direction::ToServer) || (self.tc_gap && direction == Direction::ToClient) { + SCLogDebug!("Trying to catch up after GAP (input {})", cur_i.len()); + match self.search_dcerpc_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("DCERPC record found"); + let offset = cur_i.len() - pg.len(); + cur_i = &cur_i[offset..]; + match direction { + Direction::ToServer => { + self.ts_gap = false; + }, + Direction::ToClient => { + self.tc_gap = false; + } + } + }, + _ => { + let mut consumed = cur_i.len(); + // At least 2 bytes are required to know if a new record is beginning + if consumed < 2 { + consumed = 0; + } else { + consumed -= 1; + } + SCLogDebug!("DCERPC record NOT found"); + return AppLayerResult::incomplete(consumed as u32, 2); + }, + } + } + + // Overwrite the dcerpc_state data in case of multiple complete queries in the + // same direction + if self.prev_dir == direction { + self.data_needed_for_dir = direction; + } + + let buffer = match direction { + Direction::ToServer => { + if self.buffer_ts.len() + input_len > 1024 * 1024 { + SCLogDebug!("DCERPC TOSERVER stream: Buffer Overflow"); + return AppLayerResult::err(); + } + v = self.buffer_ts.split_off(0); + v.extend_from_slice(cur_i); + v.as_slice() + } + Direction::ToClient => { + if self.buffer_tc.len() + input_len > 1024 * 1024 { + SCLogDebug!("DCERPC TOCLIENT stream: Buffer Overflow"); + return AppLayerResult::err(); + } + v = self.buffer_tc.split_off(0); + v.extend_from_slice(cur_i); + v.as_slice() + } + }; + + if self.data_needed_for_dir != direction && !buffer.is_empty() { + return AppLayerResult::err(); + } + + // Set data_needed_for_dir in the same direction in case there is an issue with upcoming parsing + self.data_needed_for_dir = direction; + + // Check if header data was complete. In case of EoF or incomplete data, wait for more + // data else return error + if self.bytes_consumed < DCERPC_HDR_LEN.into() && input_len > 0 { + parsed = self.process_header(buffer); + if parsed == -1 { + self.extend_buffer(buffer, direction); + return AppLayerResult::ok(); + } + if parsed == -2 { + return AppLayerResult::err(); + } + self.bytes_consumed += parsed; + } + + let fraglen = self.get_hdr_fraglen().unwrap_or(0); + + if (buffer.len()) < fraglen as usize { + SCLogDebug!("Possibly fragmented data, waiting for more.."); + self.extend_buffer(buffer, direction); + return AppLayerResult::ok(); + } else { + self.query_completed = true; + } + parsed = self.bytes_consumed; + + let current_call_id = self.get_hdr_call_id().unwrap_or(0); + + match self.get_hdr_type() { + Some(x) => match x { + DCERPC_TYPE_BIND | DCERPC_TYPE_ALTER_CONTEXT => { + retval = self.process_bind_pdu(&buffer[parsed as usize..]); + if retval == -1 { + return AppLayerResult::err(); + } + } + DCERPC_TYPE_BINDACK | DCERPC_TYPE_ALTER_CONTEXT_RESP => { + retval = self.process_bindack_pdu(&buffer[parsed as usize..]); + if retval == -1 { + return AppLayerResult::err(); + } + let tx = if let Some(tx) = self.get_tx_by_call_id(current_call_id, Direction::ToClient) { + tx.resp_cmd = x; + tx + } else { + let mut tx = self.create_tx(current_call_id); + tx.resp_cmd = x; + self.transactions.push_back(tx); + self.transactions.back_mut().unwrap() + }; + tx.resp_done = true; + tx.frag_cnt_tc = 1; + if let Some(flow) = self.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToClient as i32); + } + } + DCERPC_TYPE_REQUEST => { + retval = self.process_request_pdu(&buffer[parsed as usize..]); + if retval < 0 { + return AppLayerResult::err(); + } + // In case the response came first, the transaction would complete later when + // the corresponding request also comes through + } + DCERPC_TYPE_RESPONSE => { + let transaction = self.get_tx_by_call_id(current_call_id, Direction::ToClient); + match transaction { + Some(tx) => { + tx.resp_cmd = x; + } + None => { + let mut tx = self.create_tx(current_call_id); + tx.resp_cmd = x; + self.transactions.push_back(tx); + } + }; + retval = self.handle_common_stub( + &buffer[parsed as usize..], + 0, + Direction::ToClient, + ); + if retval < 0 { + return AppLayerResult::err(); + } + } + _ => { + SCLogDebug!("Unrecognized packet type: {:?}", x); + self.clean_buffer(direction); + return AppLayerResult::err(); + } + }, + None => { + return AppLayerResult::err(); + } + } + self.bytes_consumed += retval; + + // If the query has been completed, clean the buffer and reset the direction + if self.query_completed { + self.clean_buffer(direction); + self.reset_direction(direction); + } + self.post_gap_housekeeping(direction); + self.prev_dir = direction; + return AppLayerResult::ok(); + } +} + +fn evaluate_stub_params( + input: &[u8], input_len: usize, hdrflags: u8, lenleft: u16, + stub_data_buffer: &mut Vec<u8>,stub_data_buffer_reset: &mut bool, +) -> u16 { + + let fragtype = hdrflags & (PFC_FIRST_FRAG | PFC_LAST_FRAG); + // min of usize and u16 is a valid u16 + let stub_len: u16 = cmp::min(lenleft as usize, input_len) as u16; + if stub_len == 0 { + return 0; + } + if stub_len == lenleft && (fragtype == 0 || (fragtype & PFC_LAST_FRAG > 0)) { + *stub_data_buffer_reset = true; + } + + let input_slice = &input[..stub_len as usize]; + stub_data_buffer.extend_from_slice(input_slice); + + stub_len +} + +#[no_mangle] +pub extern "C" fn rs_parse_dcerpc_request_gap( + state: &mut DCERPCState, + _input_len: u32, +) -> AppLayerResult { + state.parse_data_gap(Direction::ToServer) +} + +#[no_mangle] +pub extern "C" fn rs_parse_dcerpc_response_gap( + state: &mut DCERPCState, + _input_len: u32, +) -> AppLayerResult { + state.parse_data_gap(Direction::ToClient) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_parse_request( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DCERPCState); + let flags = stream_slice.flags(); + + SCLogDebug!("Handling request: input_len {} flags {:x} EOF {}", + stream_slice.len(), flags, flags & core::STREAM_EOF != 0); + if flags & core::STREAM_EOF != 0 && stream_slice.is_empty() { + return AppLayerResult::ok(); + } + /* START with MIDSTREAM set: record might be starting the middle. */ + if flags & (core::STREAM_START|core::STREAM_MIDSTREAM) == (core::STREAM_START|core::STREAM_MIDSTREAM) { + state.ts_gap = true; + } + if !stream_slice.is_gap() { + state.flow = Some(flow); + return state.handle_input_data(stream_slice.as_slice(), Direction::ToServer); + } + AppLayerResult::err() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_parse_response( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DCERPCState); + let flags = stream_slice.flags(); + + if flags & core::STREAM_EOF != 0 && stream_slice.is_empty() { + return AppLayerResult::ok(); + } + /* START with MIDSTREAM set: record might be starting the middle. */ + if flags & (core::STREAM_START|core::STREAM_MIDSTREAM) == (core::STREAM_START|core::STREAM_MIDSTREAM) { + state.tc_gap = true; + } + if !stream_slice.is_gap() { + state.flow = Some(flow); + return state.handle_input_data(stream_slice.as_slice(), Direction::ToClient); + } + AppLayerResult::err() +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: core::AppProto) -> *mut std::os::raw::c_void { + let state = DCERPCState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut DCERPCState)} ); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_state_transaction_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let dce_state = cast_pointer!(state, DCERPCState); + SCLogDebug!("freeing tx {}", tx_id); + dce_state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_state_trunc(state: *mut std::os::raw::c_void, direction: u8) { + let dce_state = cast_pointer!(state, DCERPCState); + match direction.into() { + Direction::ToServer => { + dce_state.ts_ssn_trunc = true; + for tx in &mut dce_state.transactions { + tx.req_done = true; + if let Some(flow) = dce_state.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToServer as i32); + } + } + SCLogDebug!("dce_state.ts_ssn_trunc = true; txs {}", dce_state.transactions.len()); + } + Direction::ToClient => { + dce_state.tc_ssn_trunc = true; + for tx in &mut dce_state.transactions { + tx.resp_done = true; + if let Some(flow) = dce_state.flow { + sc_app_layer_parser_trigger_raw_stream_reassembly(flow, Direction::ToClient as i32); + } + } + SCLogDebug!("dce_state.tc_ssn_trunc = true; txs {}", dce_state.transactions.len()); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_get_tx( + vtx: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let dce_state = cast_pointer!(vtx, DCERPCState); + match dce_state.get_tx(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_get_tx_cnt(vtx: *mut std::os::raw::c_void) -> u64 { + let dce_state = cast_pointer!(vtx, DCERPCState); + dce_state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_get_alstate_progress(tx: *mut std::os::raw::c_void, direction: u8 + )-> std::os::raw::c_int { + let tx = cast_pointer!(tx, DCERPCTransaction); + if direction == Direction::ToServer.into() && tx.req_done { + SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.call_id, tx); + return 1; + } else if direction == Direction::ToClient.into() && tx.resp_done { + SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.call_id, tx); + return 1; + } + SCLogDebug!("tx {} direction {} progress 0", tx.call_id, direction); + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, DCERPCTransaction); + return &mut tx.tx_data; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_get_stub_data( + tx: &mut DCERPCTransaction, buf: *mut *const u8, len: *mut u32, endianness: *mut u8, dir: u8, +) { + match dir.into() { + Direction::ToServer => { + *len = tx.stub_data_buffer_ts.len() as u32; + *buf = tx.stub_data_buffer_ts.as_ptr(); + SCLogDebug!("DCERPC Request stub buffer: Setting buffer to: {:?}", *buf); + } + Direction::ToClient => { + *len = tx.stub_data_buffer_tc.len() as u32; + *buf = tx.stub_data_buffer_tc.as_ptr(); + SCLogDebug!("DCERPC Response stub buffer: Setting buffer to: {:?}", *buf); + } + } + *endianness = tx.get_endianness(); +} + +/// Probe input to see if it looks like DCERPC. +fn probe(input: &[u8]) -> (bool, bool) { + match parser::parse_dcerpc_header(input) { + Ok((_, hdr)) => { + let is_request = hdr.hdrtype == 0x00 || hdr.hdrtype == 0x0e; + let is_dcerpc = hdr.rpc_vers == 0x05 && + hdr.rpc_vers_minor == 0x00 && + hdr.packed_drep[0] & 0xee == 0 && + hdr.packed_drep[1] <= 3; + return (is_dcerpc, is_request); + }, + Err(_) => (false, false), + } +} + +pub unsafe extern "C" fn rs_dcerpc_probe_tcp(_f: *const core::Flow, direction: u8, input: *const u8, + len: u32, rdir: *mut u8) -> AppProto +{ + SCLogDebug!("Probing packet for DCERPC"); + if len == 0 { + return core::ALPROTO_UNKNOWN; + } + let slice: &[u8] = std::slice::from_raw_parts(input as *mut u8, len as usize); + //is_incomplete is checked by caller + let (is_dcerpc, is_request, ) = probe(slice); + if is_dcerpc { + let dir = if is_request { + Direction::ToServer + } else { + Direction::ToClient + }; + if (direction & DIR_BOTH) != dir as u8 { + *rdir = dir as u8; + } + return ALPROTO_DCERPC; + } + return core::ALPROTO_FAILED; +} + +fn register_pattern_probe() -> i8 { + unsafe { + if AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_DCERPC, + b"|05 00|\0".as_ptr() as *const std::os::raw::c_char, 2, 0, + Direction::ToServer.into(), rs_dcerpc_probe_tcp, 0, 0) < 0 { + SCLogDebug!("TOSERVER => AppLayerProtoDetectPMRegisterPatternCSwPP FAILED"); + return -1; + } + if AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_DCERPC, + b"|05 00|\0".as_ptr() as *const std::os::raw::c_char, 2, 0, + Direction::ToClient.into(), rs_dcerpc_probe_tcp, 0, 0) < 0 { + SCLogDebug!("TOCLIENT => AppLayerProtoDetectPMRegisterPatternCSwPP FAILED"); + return -1; + } + } + + 0 +} + +export_state_data_get!(rs_dcerpc_get_state_data, DCERPCState); + +// Parser name as a C style string. +pub const PARSER_NAME: &[u8] = b"dcerpc\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_register_parser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_dcerpc_state_new, + state_free: rs_dcerpc_state_free, + tx_free: rs_dcerpc_state_transaction_free, + parse_ts: rs_dcerpc_parse_request, + parse_tc: rs_dcerpc_parse_response, + get_tx_count: rs_dcerpc_get_tx_cnt, + get_tx: rs_dcerpc_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_dcerpc_get_alstate_progress, + get_eventinfo: None, + get_eventinfo_byid : None, + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<DCERPCState, DCERPCTransaction>), + get_tx_data: rs_dcerpc_get_tx_data, + get_state_data: rs_dcerpc_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DCERPC = alproto; + if register_pattern_probe() < 0 { + return; + } + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + if let Some(val) = conf_get("app-layer.protocols.dcerpc.max-tx") { + if let Ok(v) = val.parse::<usize>() { + DCERPC_MAX_TX = v; + } else { + SCLogError!("Invalid value for smb.max-tx"); + } + } + SCLogDebug!("Rust DCERPC parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for DCERPC."); + } +} + +#[cfg(test)] +mod tests { + use crate::applayer::AppLayerResult; + use crate::core::*; + use crate::dcerpc::dcerpc::DCERPCState; + use std::cmp; + + #[test] + fn test_process_header() { + let request: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!(16, dcerpc_state.process_header(request)); + } + + #[test] + fn test_process_bind_pdu() { + let header: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let bind: &[u8] = &[ + 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x2c, 0xd0, 0x28, 0xda, 0x76, 0x91, 0xf6, 0x6e, 0xcb, 0x0f, 0xbf, 0x85, + 0xcd, 0x9b, 0xf6, 0x39, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x2c, 0x75, 0xce, 0x7e, 0x82, 0x3b, 0x06, 0xac, 0x1b, 0xf0, + 0xf5, 0xb7, 0xa7, 0xf7, 0x28, 0xaf, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0xe3, 0xb2, 0x10, 0xd1, 0xd0, 0x0c, 0xcc, 0x3d, + 0x2f, 0x80, 0x20, 0x7c, 0xef, 0xe7, 0x09, 0xe0, 0x04, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xde, 0x85, 0x70, 0xc4, 0x02, 0x7c, + 0x60, 0x23, 0x67, 0x0c, 0x22, 0xbf, 0x18, 0x36, 0x79, 0x17, 0x01, 0x00, 0x02, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x41, 0x65, 0x29, 0x51, + 0xaa, 0xe7, 0x7b, 0xa8, 0xf2, 0x37, 0x0b, 0xd0, 0x3f, 0xb3, 0x36, 0xed, 0x05, 0x00, + 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x14, 0x96, + 0x80, 0x01, 0x2e, 0x78, 0xfb, 0x5d, 0xb4, 0x3c, 0x14, 0xb3, 0x3d, 0xaa, 0x02, 0xfb, + 0x06, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, + 0x3b, 0x04, 0x68, 0x3e, 0x63, 0xfe, 0x9f, 0xd8, 0x64, 0x55, 0xcd, 0xe7, 0x39, 0xaf, + 0x98, 0x9f, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x01, 0x00, 0x16, 0x7a, 0x4f, 0x1b, 0xdb, 0x25, 0x92, 0x55, 0xdd, 0xae, 0x9e, 0x5b, + 0x3e, 0x93, 0x66, 0x93, 0x04, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x08, 0x00, 0x01, 0x00, 0xe8, 0xa4, 0x8a, 0xcf, 0x95, 0x6c, 0xc7, 0x8f, 0x14, 0xcc, + 0x56, 0xfc, 0x7b, 0x5f, 0x4f, 0xe8, 0x04, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0xd8, 0xda, 0xfb, 0xbc, 0xa2, 0x55, 0x6f, 0x5d, + 0xc0, 0x2d, 0x88, 0x6f, 0x00, 0x17, 0x52, 0x8d, 0x06, 0x00, 0x03, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x3f, 0x17, 0x55, 0x0c, 0xf4, 0x23, + 0x3c, 0xca, 0xe6, 0xa0, 0xaa, 0xcc, 0xb5, 0xe3, 0xf9, 0xce, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x6a, 0x28, 0x19, 0x39, + 0x0c, 0xb1, 0xd0, 0x11, 0x9b, 0xa8, 0x00, 0xc0, 0x4f, 0xd9, 0x2e, 0xf5, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, 0xc9, 0x9f, + 0x3e, 0x6e, 0x82, 0x0a, 0x2b, 0x28, 0x37, 0x78, 0xe1, 0x13, 0x70, 0x05, 0x38, 0x4d, + 0x01, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x01, 0x00, + 0x11, 0xaa, 0x4b, 0x15, 0xdf, 0xa6, 0x86, 0x3f, 0xfb, 0xe0, 0x09, 0xb7, 0xf8, 0x56, + 0xd2, 0x3f, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, + 0x01, 0x00, 0xee, 0x99, 0xc4, 0x25, 0x11, 0xe4, 0x95, 0x62, 0x29, 0xfa, 0xfd, 0x26, + 0x57, 0x02, 0xf1, 0xce, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x0f, 0x00, 0x01, 0x00, 0xba, 0x81, 0x9e, 0x1a, 0xdf, 0x2b, 0xba, 0xe4, 0xd3, 0x17, + 0x41, 0x60, 0x6d, 0x2d, 0x9e, 0x28, 0x03, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0xa0, 0x24, 0x03, 0x9a, 0xa9, 0x99, 0xfb, 0xbe, + 0x49, 0x11, 0xad, 0x77, 0x30, 0xaa, 0xbc, 0xb6, 0x02, 0x00, 0x03, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x32, 0x04, 0x7e, 0xae, 0xec, 0x28, + 0xd1, 0x55, 0x83, 0x4e, 0xc3, 0x47, 0x5d, 0x1d, 0xc6, 0x65, 0x02, 0x00, 0x03, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0xc6, 0xa4, 0x81, 0x48, + 0x66, 0x2a, 0x74, 0x7d, 0x56, 0x6e, 0xc5, 0x1d, 0x19, 0xf2, 0xb5, 0xb6, 0x03, 0x00, + 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x01, 0x00, 0xcb, 0xae, + 0xb3, 0xc0, 0x0c, 0xf4, 0xa4, 0x5e, 0x91, 0x72, 0xdd, 0x53, 0x24, 0x70, 0x89, 0x02, + 0x05, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, + 0xb8, 0xd0, 0xa0, 0x1a, 0x5e, 0x7a, 0x2d, 0xfe, 0x35, 0xc6, 0x7d, 0x08, 0x0d, 0x33, + 0x73, 0x18, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x15, 0x00, + 0x01, 0x00, 0x21, 0xd3, 0xaa, 0x09, 0x03, 0xa7, 0x0b, 0xc2, 0x06, 0x45, 0xd9, 0x6c, + 0x75, 0xc2, 0x15, 0xa8, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x16, 0x00, 0x01, 0x00, 0xe1, 0xbd, 0x59, 0xfc, 0xbc, 0xa9, 0x95, 0xc2, 0x68, 0x79, + 0xf3, 0x75, 0xe0, 0xae, 0x6c, 0xe5, 0x04, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, 0x06, 0x52, 0xb4, 0x71, 0x70, 0x15, 0x4e, 0xf5, + 0x7f, 0x08, 0x86, 0x14, 0xe6, 0x17, 0xd5, 0x97, 0x04, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!(16, dcerpc_state.process_header(header)); + assert_eq!(1068, dcerpc_state.process_bind_pdu(bind)); + } + + #[test] + fn test_handle_bindctxitem() { + let header: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let bind: &[u8] = &[ + 0x00, 0x00, 0x01, 0x00, 0x2c, 0xd0, 0x28, 0xda, 0x76, 0x91, 0xf6, 0x6e, 0xcb, 0x0f, + 0xbf, 0x85, 0xcd, 0x9b, 0xf6, 0x39, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!(16, dcerpc_state.process_header(header)); + assert_eq!(44, dcerpc_state.handle_bindctxitem(bind, 0)); + } + + #[test] + fn test_process_bindack_pdu() { + let bind: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x3c, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x2c, 0xd0, 0x28, 0xda, 0x76, 0x91, 0xf6, 0x6e, 0xcb, 0x0f, + 0xbf, 0x85, 0xcd, 0x9b, 0xf6, 0x39, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x2c, 0x75, 0xce, 0x7e, 0x82, 0x3b, 0x06, 0xac, + 0x1b, 0xf0, 0xf5, 0xb7, 0xa7, 0xf7, 0x28, 0xaf, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0xe3, 0xb2, 0x10, 0xd1, 0xd0, 0x0c, + 0xcc, 0x3d, 0x2f, 0x80, 0x20, 0x7c, 0xef, 0xe7, 0x09, 0xe0, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xde, 0x85, 0x70, 0xc4, + 0x02, 0x7c, 0x60, 0x23, 0x67, 0x0c, 0x22, 0xbf, 0x18, 0x36, 0x79, 0x17, 0x01, 0x00, + 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x41, 0x65, + 0x29, 0x51, 0xaa, 0xe7, 0x7b, 0xa8, 0xf2, 0x37, 0x0b, 0xd0, 0x3f, 0xb3, 0x36, 0xed, + 0x05, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x14, 0x96, 0x80, 0x01, 0x2e, 0x78, 0xfb, 0x5d, 0xb4, 0x3c, 0x14, 0xb3, 0x3d, 0xaa, + 0x02, 0xfb, 0x06, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x3b, 0x04, 0x68, 0x3e, 0x63, 0xfe, 0x9f, 0xd8, 0x64, 0x55, 0xcd, 0xe7, + 0x39, 0xaf, 0x98, 0x9f, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x01, 0x00, 0x16, 0x7a, 0x4f, 0x1b, 0xdb, 0x25, 0x92, 0x55, 0xdd, 0xae, + 0x9e, 0x5b, 0x3e, 0x93, 0x66, 0x93, 0x04, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0xe8, 0xa4, 0x8a, 0xcf, 0x95, 0x6c, 0xc7, 0x8f, + 0x14, 0xcc, 0x56, 0xfc, 0x7b, 0x5f, 0x4f, 0xe8, 0x04, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0xd8, 0xda, 0xfb, 0xbc, 0xa2, 0x55, + 0x6f, 0x5d, 0xc0, 0x2d, 0x88, 0x6f, 0x00, 0x17, 0x52, 0x8d, 0x06, 0x00, 0x03, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x3f, 0x17, 0x55, 0x0c, + 0xf4, 0x23, 0x3c, 0xca, 0xe6, 0xa0, 0xaa, 0xcc, 0xb5, 0xe3, 0xf9, 0xce, 0x04, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x6a, 0x28, + 0x19, 0x39, 0x0c, 0xb1, 0xd0, 0x11, 0x9b, 0xa8, 0x00, 0xc0, 0x4f, 0xd9, 0x2e, 0xf5, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, + 0xc9, 0x9f, 0x3e, 0x6e, 0x82, 0x0a, 0x2b, 0x28, 0x37, 0x78, 0xe1, 0x13, 0x70, 0x05, + 0x38, 0x4d, 0x01, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x01, 0x00, 0x11, 0xaa, 0x4b, 0x15, 0xdf, 0xa6, 0x86, 0x3f, 0xfb, 0xe0, 0x09, 0xb7, + 0xf8, 0x56, 0xd2, 0x3f, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x01, 0x00, 0xee, 0x99, 0xc4, 0x25, 0x11, 0xe4, 0x95, 0x62, 0x29, 0xfa, + 0xfd, 0x26, 0x57, 0x02, 0xf1, 0xce, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x01, 0x00, 0xba, 0x81, 0x9e, 0x1a, 0xdf, 0x2b, 0xba, 0xe4, + 0xd3, 0x17, 0x41, 0x60, 0x6d, 0x2d, 0x9e, 0x28, 0x03, 0x00, 0x03, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x01, 0x00, 0xa0, 0x24, 0x03, 0x9a, 0xa9, 0x99, + 0xfb, 0xbe, 0x49, 0x11, 0xad, 0x77, 0x30, 0xaa, 0xbc, 0xb6, 0x02, 0x00, 0x03, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x01, 0x00, 0x32, 0x04, 0x7e, 0xae, + 0xec, 0x28, 0xd1, 0x55, 0x83, 0x4e, 0xc3, 0x47, 0x5d, 0x1d, 0xc6, 0x65, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x01, 0x00, 0xc6, 0xa4, + 0x81, 0x48, 0x66, 0x2a, 0x74, 0x7d, 0x56, 0x6e, 0xc5, 0x1d, 0x19, 0xf2, 0xb5, 0xb6, + 0x03, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x13, 0x00, 0x01, 0x00, + 0xcb, 0xae, 0xb3, 0xc0, 0x0c, 0xf4, 0xa4, 0x5e, 0x91, 0x72, 0xdd, 0x53, 0x24, 0x70, + 0x89, 0x02, 0x05, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, + 0x01, 0x00, 0xb8, 0xd0, 0xa0, 0x1a, 0x5e, 0x7a, 0x2d, 0xfe, 0x35, 0xc6, 0x7d, 0x08, + 0x0d, 0x33, 0x73, 0x18, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x15, 0x00, 0x01, 0x00, 0x21, 0xd3, 0xaa, 0x09, 0x03, 0xa7, 0x0b, 0xc2, 0x06, 0x45, + 0xd9, 0x6c, 0x75, 0xc2, 0x15, 0xa8, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x16, 0x00, 0x01, 0x00, 0xe1, 0xbd, 0x59, 0xfc, 0xbc, 0xa9, 0x95, 0xc2, + 0x68, 0x79, 0xf3, 0x75, 0xe0, 0xae, 0x6c, 0xe5, 0x04, 0x00, 0x02, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00, 0x06, 0x52, 0xb4, 0x71, 0x70, 0x15, + 0x4e, 0xf5, 0x7f, 0x08, 0x86, 0x14, 0xe6, 0x17, 0xd5, 0x97, 0x04, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let bindack: &[u8] = &[ + 0xb8, 0x10, 0xb8, 0x10, 0xce, 0x47, 0x00, 0x00, 0x0c, 0x00, 0x5c, 0x50, 0x49, 0x50, + 0x45, 0x5c, 0x6c, 0x73, 0x61, 0x73, 0x73, 0x00, 0xf6, 0x6e, 0x18, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!(16, dcerpc_state.process_header(bind)); + assert_eq!(1068, dcerpc_state.process_bind_pdu(&bind[16..])); + assert_eq!(604, dcerpc_state.process_bindack_pdu(bindack)); + if let Some(back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + assert_eq!( + vec!(57, 25, 40, 106, 177, 12, 17, 208, 155, 168, 0, 192, 79, 217, 46, 245), + back.accepted_uuid_list[0].uuid + ); + assert_eq!(11, back.accepted_uuid_list[0].internal_id); + } + } + + #[test] + pub fn test_process_request_pdu() { + let request: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x0b, 0x00, 0x09, 0x00, 0x45, 0x00, 0x2c, 0x00, + 0x4d, 0x00, 0x73, 0x00, 0x53, 0x00, 0x59, 0x00, 0x2a, 0x00, 0x4a, 0x00, 0x7a, 0x00, + 0x3e, 0x00, 0x58, 0x00, 0x21, 0x00, 0x4a, 0x00, 0x30, 0x00, 0x41, 0x00, 0x4b, 0x00, + 0x4b, 0x00, 0x3c, 0x00, 0x48, 0x00, 0x24, 0x00, 0x38, 0x00, 0x54, 0x00, 0x60, 0x00, + 0x2d, 0x00, 0x29, 0x00, 0x64, 0x00, 0x5b, 0x00, 0x77, 0x00, 0x3a, 0x00, 0x4c, 0x00, + 0x24, 0x00, 0x23, 0x00, 0x66, 0x00, 0x43, 0x00, 0x68, 0x00, 0x22, 0x00, 0x55, 0x00, + 0x29, 0x00, 0x2c, 0x00, 0x4f, 0x00, 0x5a, 0x00, 0x50, 0x00, 0x61, 0x00, 0x2a, 0x00, + 0x6f, 0x00, 0x2f, 0x00, 0x4d, 0x00, 0x68, 0x00, 0x3a, 0x00, 0x5c, 0x00, 0x67, 0x00, + 0x68, 0x00, 0x68, 0x00, 0x49, 0x00, 0x45, 0x00, 0x4c, 0x00, 0x72, 0x00, 0x53, 0x00, + 0x4c, 0x00, 0x25, 0x00, 0x4d, 0x00, 0x67, 0x00, 0x2e, 0x00, 0x4f, 0x00, 0x64, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x24, 0x00, 0x46, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x45, 0x00, + 0x6f, 0x00, 0x40, 0x00, 0x41, 0x00, 0x33, 0x00, 0x38, 0x00, 0x47, 0x00, 0x71, 0x00, + 0x5a, 0x00, 0x37, 0x00, 0x7a, 0x00, 0x35, 0x00, 0x6b, 0x00, 0x3c, 0x00, 0x26, 0x00, + 0x37, 0x00, 0x69, 0x00, 0x75, 0x00, 0x36, 0x00, 0x37, 0x00, 0x47, 0x00, 0x21, 0x00, + 0x2d, 0x00, 0x69, 0x00, 0x37, 0x00, 0x78, 0x00, 0x5f, 0x00, 0x72, 0x00, 0x4b, 0x00, + 0x5c, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x52, 0x00, 0x7a, 0x00, 0x49, 0x00, 0x31, 0x00, + 0x5a, 0x00, 0x7b, 0x00, 0x29, 0x00, 0x3b, 0x00, 0x78, 0x00, 0x3b, 0x00, 0x55, 0x00, + 0x3e, 0x00, 0x35, 0x00, 0x2b, 0x00, 0x4e, 0x00, 0x4f, 0x00, 0x59, 0x00, 0x38, 0x00, + 0x2a, 0x00, 0x59, 0x00, 0x6b, 0x00, 0x42, 0x00, 0x4c, 0x00, 0x3e, 0x00, 0x6a, 0x00, + 0x49, 0x00, 0x2c, 0x00, 0x79, 0x00, 0x6e, 0x00, 0x35, 0x00, 0x4f, 0x00, 0x49, 0x00, + 0x55, 0x00, 0x35, 0x00, 0x61, 0x00, 0x72, 0x00, 0x77, 0x00, 0x38, 0x00, 0x32, 0x00, + 0x24, 0x00, 0x46, 0x00, 0x32, 0x00, 0x32, 0x00, 0x27, 0x00, 0x64, 0x00, 0x5a, 0x00, + 0x77, 0x00, 0x2e, 0x00, 0x37, 0x00, 0x77, 0x00, 0x2e, 0x00, 0x28, 0x00, 0x63, 0x00, + 0x4f, 0x00, 0x67, 0x00, 0x64, 0x00, 0x39, 0x00, 0x37, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x28, 0x00, 0x2e, 0x00, 0x6f, 0x00, 0x3e, 0x00, 0x59, 0x00, 0x28, 0x00, 0x67, 0x00, + 0x52, 0x00, 0x35, 0x00, 0x5a, 0x00, 0x7c, 0x00, 0x56, 0x00, 0x6a, 0x00, 0x5c, 0x00, + 0x3c, 0x00, 0x30, 0x00, 0x59, 0x00, 0x5c, 0x00, 0x5e, 0x00, 0x38, 0x00, 0x54, 0x00, + 0x5c, 0x00, 0x5b, 0x00, 0x42, 0x00, 0x62, 0x00, 0x70, 0x00, 0x34, 0x00, 0x5c, 0x00, + 0x57, 0x00, 0x7a, 0x00, 0x4b, 0x00, 0x2f, 0x00, 0x6b, 0x00, 0x6a, 0x00, 0x4f, 0x00, + 0x41, 0x00, 0x33, 0x00, 0x52, 0x00, 0x36, 0x00, 0x27, 0x00, 0x30, 0x00, 0x6d, 0x00, + 0x4a, 0x00, 0x30, 0x00, 0x78, 0x00, 0x46, 0x00, 0x65, 0x00, 0x4e, 0x00, 0x29, 0x00, + 0x66, 0x00, 0x3f, 0x00, 0x72, 0x00, 0x71, 0x00, 0x75, 0x00, 0x4c, 0x00, 0x2b, 0x00, + 0x5c, 0x00, 0x46, 0x00, 0x52, 0x00, 0x7b, 0x00, 0x5c, 0x00, 0x69, 0x00, 0x66, 0x00, + 0x56, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x72, 0x00, 0x61, 0x00, 0x68, 0x00, 0x28, 0x00, + 0x7d, 0x00, 0x58, 0x00, 0x2a, 0x00, 0x7b, 0x00, 0x28, 0x00, 0x5b, 0x00, 0x54, 0x00, + 0x3a, 0x00, 0x26, 0x00, 0x52, 0x00, 0x44, 0x00, 0x60, 0x00, 0x50, 0x00, 0x65, 0x00, + 0x48, 0x00, 0x7d, 0x00, 0x2a, 0x00, 0x74, 0x00, 0x49, 0x00, 0x7b, 0x00, 0x21, 0x00, + 0x61, 0x00, 0x52, 0x00, 0x43, 0x00, 0x5f, 0x00, 0x5a, 0x00, 0x74, 0x00, 0x5c, 0x00, + 0x62, 0x00, 0x68, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x2b, 0x00, 0x6f, 0x00, 0x7c, 0x00, + 0x42, 0x00, 0x67, 0x00, 0x32, 0x00, 0x58, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2f, 0x00, + 0x2d, 0x00, 0x60, 0x00, 0x62, 0x00, 0x51, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x31, 0x00, + 0x48, 0x00, 0x5b, 0x00, 0x5b, 0x00, 0x5d, 0x00, 0x25, 0x00, 0x58, 0x00, 0x4a, 0x00, + 0x76, 0x00, 0x32, 0x00, 0x62, 0x00, 0x27, 0x00, 0x42, 0x00, 0x40, 0x00, 0x53, 0x00, + 0x7c, 0x00, 0x7d, 0x00, 0x50, 0x00, 0x3d, 0x00, 0x40, 0x00, 0x76, 0x00, 0x38, 0x00, + 0x58, 0x00, 0x39, 0x00, 0x63, 0x00, 0x3c, 0x00, 0x5b, 0x00, 0x23, 0x00, 0x53, 0x00, + 0x7a, 0x00, 0x54, 0x00, 0x74, 0x00, 0x61, 0x00, 0x76, 0x00, 0x4a, 0x00, 0x3e, 0x00, + 0x33, 0x00, 0x75, 0x00, 0x66, 0x00, 0x2d, 0x00, 0x48, 0x00, 0x33, 0x00, 0x71, 0x00, + 0x76, 0x00, 0x48, 0x00, 0x71, 0x00, 0x41, 0x00, 0x6f, 0x00, 0x2a, 0x00, 0x67, 0x00, + 0x70, 0x00, 0x21, 0x00, 0x70, 0x00, 0x4b, 0x00, 0x52, 0x00, 0x58, 0x00, 0x68, 0x00, + 0x23, 0x00, 0x39, 0x00, 0x46, 0x00, 0x4d, 0x00, 0x51, 0x00, 0x57, 0x00, 0x3a, 0x00, + 0x79, 0x00, 0x7b, 0x00, 0x6c, 0x00, 0x55, 0x00, 0x33, 0x00, 0x65, 0x00, 0x49, 0x00, + 0x72, 0x00, 0x30, 0x00, 0x4f, 0x00, 0x41, 0x00, 0x6e, 0x00, 0x31, 0x00, 0x4a, 0x00, + 0x60, 0x00, 0x79, 0x00, 0x70, 0x00, 0x4f, 0x00, 0x58, 0x00, 0x75, 0x00, 0x44, 0x00, + 0x59, 0x00, 0x58, 0x00, 0x46, 0x00, 0x3d, 0x00, 0x46, 0x00, 0x74, 0x00, 0x51, 0x00, + 0x57, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x23, 0x00, 0x45, 0x00, 0x60, 0x00, + 0x4c, 0x00, 0x72, 0x00, 0x4e, 0x00, 0x74, 0x00, 0x40, 0x00, 0x76, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x56, 0x00, 0x44, 0x00, 0x29, 0x00, 0x62, 0x00, 0x58, 0x00, 0x31, 0x00, + 0x78, 0x00, 0x32, 0x00, 0x52, 0x00, 0x4a, 0x00, 0x6b, 0x00, 0x55, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x6f, 0x00, 0x4a, 0x00, 0x54, 0x00, 0x7d, 0x00, 0x68, 0x00, 0x3f, 0x00, + 0x28, 0x00, 0x21, 0x00, 0x53, 0x00, 0x48, 0x00, 0x5a, 0x00, 0x34, 0x00, 0x36, 0x00, + 0x35, 0x00, 0x64, 0x00, 0x4e, 0x00, 0x75, 0x00, 0x69, 0x00, 0x23, 0x00, 0x75, 0x00, + 0x55, 0x00, 0x43, 0x00, 0x75, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x62, 0x00, 0x6f, 0x00, + 0x37, 0x00, 0x4e, 0x00, 0x25, 0x00, 0x25, 0x00, 0x21, 0x00, 0x3d, 0x00, 0x3c, 0x00, + 0x71, 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x30, 0x00, 0x36, 0x00, 0x62, 0x00, 0x63, 0x00, + 0x53, 0x00, 0x54, 0x00, 0x5d, 0x00, 0x61, 0x00, 0x4c, 0x00, 0x28, 0x00, 0x2b, 0x00, + 0x4c, 0x00, 0x4e, 0x00, 0x66, 0x00, 0x5f, 0x00, 0x4b, 0x00, 0x43, 0x00, 0x75, 0x00, + 0x45, 0x00, 0x37, 0x00, 0x28, 0x00, 0x56, 0x00, 0x36, 0x00, 0x6a, 0x00, 0x3e, 0x00, + 0x64, 0x00, 0x34, 0x00, 0x6a, 0x00, 0x7d, 0x00, 0x4a, 0x00, 0x66, 0x00, 0x7a, 0x00, + 0x3e, 0x00, 0x75, 0x00, 0x38, 0x00, 0x7b, 0x00, 0x42, 0x00, 0x76, 0x00, 0x29, 0x00, + 0x4c, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x4b, 0x00, 0x2b, 0x00, 0x51, 0x00, + 0x47, 0x00, 0x22, 0x00, 0x48, 0x00, 0x3d, 0x00, 0x49, 0x00, 0x44, 0x00, 0x5d, 0x00, + 0x59, 0x00, 0x63, 0x00, 0x5c, 0x00, 0x24, 0x00, 0x35, 0x00, 0x34, 0x00, 0x70, 0x00, + 0x69, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!(16, dcerpc_state.process_header(request)); + assert_eq!(1008, dcerpc_state.process_request_pdu(&request[16..])); + } + + #[test] + pub fn test_parse_dcerpc() { + let request: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x0b, 0x00, 0x09, 0x00, 0x45, 0x00, 0x2c, 0x00, + 0x4d, 0x00, 0x73, 0x00, 0x53, 0x00, 0x59, 0x00, 0x2a, 0x00, 0x4a, 0x00, 0x7a, 0x00, + 0x3e, 0x00, 0x58, 0x00, 0x21, 0x00, 0x4a, 0x00, 0x30, 0x00, 0x41, 0x00, 0x4b, 0x00, + 0x4b, 0x00, 0x3c, 0x00, 0x48, 0x00, 0x24, 0x00, 0x38, 0x00, 0x54, 0x00, 0x60, 0x00, + 0x2d, 0x00, 0x29, 0x00, 0x64, 0x00, 0x5b, 0x00, 0x77, 0x00, 0x3a, 0x00, 0x4c, 0x00, + 0x24, 0x00, 0x23, 0x00, 0x66, 0x00, 0x43, 0x00, 0x68, 0x00, 0x22, 0x00, 0x55, 0x00, + 0x29, 0x00, 0x2c, 0x00, 0x4f, 0x00, 0x5a, 0x00, 0x50, 0x00, 0x61, 0x00, 0x2a, 0x00, + 0x6f, 0x00, 0x2f, 0x00, 0x4d, 0x00, 0x68, 0x00, 0x3a, 0x00, 0x5c, 0x00, 0x67, 0x00, + 0x68, 0x00, 0x68, 0x00, 0x49, 0x00, 0x45, 0x00, 0x4c, 0x00, 0x72, 0x00, 0x53, 0x00, + 0x4c, 0x00, 0x25, 0x00, 0x4d, 0x00, 0x67, 0x00, 0x2e, 0x00, 0x4f, 0x00, 0x64, 0x00, + 0x61, 0x00, 0x73, 0x00, 0x24, 0x00, 0x46, 0x00, 0x35, 0x00, 0x2e, 0x00, 0x45, 0x00, + 0x6f, 0x00, 0x40, 0x00, 0x41, 0x00, 0x33, 0x00, 0x38, 0x00, 0x47, 0x00, 0x71, 0x00, + 0x5a, 0x00, 0x37, 0x00, 0x7a, 0x00, 0x35, 0x00, 0x6b, 0x00, 0x3c, 0x00, 0x26, 0x00, + 0x37, 0x00, 0x69, 0x00, 0x75, 0x00, 0x36, 0x00, 0x37, 0x00, 0x47, 0x00, 0x21, 0x00, + 0x2d, 0x00, 0x69, 0x00, 0x37, 0x00, 0x78, 0x00, 0x5f, 0x00, 0x72, 0x00, 0x4b, 0x00, + 0x5c, 0x00, 0x74, 0x00, 0x3e, 0x00, 0x52, 0x00, 0x7a, 0x00, 0x49, 0x00, 0x31, 0x00, + 0x5a, 0x00, 0x7b, 0x00, 0x29, 0x00, 0x3b, 0x00, 0x78, 0x00, 0x3b, 0x00, 0x55, 0x00, + 0x3e, 0x00, 0x35, 0x00, 0x2b, 0x00, 0x4e, 0x00, 0x4f, 0x00, 0x59, 0x00, 0x38, 0x00, + 0x2a, 0x00, 0x59, 0x00, 0x6b, 0x00, 0x42, 0x00, 0x4c, 0x00, 0x3e, 0x00, 0x6a, 0x00, + 0x49, 0x00, 0x2c, 0x00, 0x79, 0x00, 0x6e, 0x00, 0x35, 0x00, 0x4f, 0x00, 0x49, 0x00, + 0x55, 0x00, 0x35, 0x00, 0x61, 0x00, 0x72, 0x00, 0x77, 0x00, 0x38, 0x00, 0x32, 0x00, + 0x24, 0x00, 0x46, 0x00, 0x32, 0x00, 0x32, 0x00, 0x27, 0x00, 0x64, 0x00, 0x5a, 0x00, + 0x77, 0x00, 0x2e, 0x00, 0x37, 0x00, 0x77, 0x00, 0x2e, 0x00, 0x28, 0x00, 0x63, 0x00, + 0x4f, 0x00, 0x67, 0x00, 0x64, 0x00, 0x39, 0x00, 0x37, 0x00, 0x31, 0x00, 0x30, 0x00, + 0x28, 0x00, 0x2e, 0x00, 0x6f, 0x00, 0x3e, 0x00, 0x59, 0x00, 0x28, 0x00, 0x67, 0x00, + 0x52, 0x00, 0x35, 0x00, 0x5a, 0x00, 0x7c, 0x00, 0x56, 0x00, 0x6a, 0x00, 0x5c, 0x00, + 0x3c, 0x00, 0x30, 0x00, 0x59, 0x00, 0x5c, 0x00, 0x5e, 0x00, 0x38, 0x00, 0x54, 0x00, + 0x5c, 0x00, 0x5b, 0x00, 0x42, 0x00, 0x62, 0x00, 0x70, 0x00, 0x34, 0x00, 0x5c, 0x00, + 0x57, 0x00, 0x7a, 0x00, 0x4b, 0x00, 0x2f, 0x00, 0x6b, 0x00, 0x6a, 0x00, 0x4f, 0x00, + 0x41, 0x00, 0x33, 0x00, 0x52, 0x00, 0x36, 0x00, 0x27, 0x00, 0x30, 0x00, 0x6d, 0x00, + 0x4a, 0x00, 0x30, 0x00, 0x78, 0x00, 0x46, 0x00, 0x65, 0x00, 0x4e, 0x00, 0x29, 0x00, + 0x66, 0x00, 0x3f, 0x00, 0x72, 0x00, 0x71, 0x00, 0x75, 0x00, 0x4c, 0x00, 0x2b, 0x00, + 0x5c, 0x00, 0x46, 0x00, 0x52, 0x00, 0x7b, 0x00, 0x5c, 0x00, 0x69, 0x00, 0x66, 0x00, + 0x56, 0x00, 0x31, 0x00, 0x2d, 0x00, 0x72, 0x00, 0x61, 0x00, 0x68, 0x00, 0x28, 0x00, + 0x7d, 0x00, 0x58, 0x00, 0x2a, 0x00, 0x7b, 0x00, 0x28, 0x00, 0x5b, 0x00, 0x54, 0x00, + 0x3a, 0x00, 0x26, 0x00, 0x52, 0x00, 0x44, 0x00, 0x60, 0x00, 0x50, 0x00, 0x65, 0x00, + 0x48, 0x00, 0x7d, 0x00, 0x2a, 0x00, 0x74, 0x00, 0x49, 0x00, 0x7b, 0x00, 0x21, 0x00, + 0x61, 0x00, 0x52, 0x00, 0x43, 0x00, 0x5f, 0x00, 0x5a, 0x00, 0x74, 0x00, 0x5c, 0x00, + 0x62, 0x00, 0x68, 0x00, 0x6c, 0x00, 0x6c, 0x00, 0x2b, 0x00, 0x6f, 0x00, 0x7c, 0x00, + 0x42, 0x00, 0x67, 0x00, 0x32, 0x00, 0x58, 0x00, 0x35, 0x00, 0x30, 0x00, 0x2f, 0x00, + 0x2d, 0x00, 0x60, 0x00, 0x62, 0x00, 0x51, 0x00, 0x2a, 0x00, 0x30, 0x00, 0x31, 0x00, + 0x48, 0x00, 0x5b, 0x00, 0x5b, 0x00, 0x5d, 0x00, 0x25, 0x00, 0x58, 0x00, 0x4a, 0x00, + 0x76, 0x00, 0x32, 0x00, 0x62, 0x00, 0x27, 0x00, 0x42, 0x00, 0x40, 0x00, 0x53, 0x00, + 0x7c, 0x00, 0x7d, 0x00, 0x50, 0x00, 0x3d, 0x00, 0x40, 0x00, 0x76, 0x00, 0x38, 0x00, + 0x58, 0x00, 0x39, 0x00, 0x63, 0x00, 0x3c, 0x00, 0x5b, 0x00, 0x23, 0x00, 0x53, 0x00, + 0x7a, 0x00, 0x54, 0x00, 0x74, 0x00, 0x61, 0x00, 0x76, 0x00, 0x4a, 0x00, 0x3e, 0x00, + 0x33, 0x00, 0x75, 0x00, 0x66, 0x00, 0x2d, 0x00, 0x48, 0x00, 0x33, 0x00, 0x71, 0x00, + 0x76, 0x00, 0x48, 0x00, 0x71, 0x00, 0x41, 0x00, 0x6f, 0x00, 0x2a, 0x00, 0x67, 0x00, + 0x70, 0x00, 0x21, 0x00, 0x70, 0x00, 0x4b, 0x00, 0x52, 0x00, 0x58, 0x00, 0x68, 0x00, + 0x23, 0x00, 0x39, 0x00, 0x46, 0x00, 0x4d, 0x00, 0x51, 0x00, 0x57, 0x00, 0x3a, 0x00, + 0x79, 0x00, 0x7b, 0x00, 0x6c, 0x00, 0x55, 0x00, 0x33, 0x00, 0x65, 0x00, 0x49, 0x00, + 0x72, 0x00, 0x30, 0x00, 0x4f, 0x00, 0x41, 0x00, 0x6e, 0x00, 0x31, 0x00, 0x4a, 0x00, + 0x60, 0x00, 0x79, 0x00, 0x70, 0x00, 0x4f, 0x00, 0x58, 0x00, 0x75, 0x00, 0x44, 0x00, + 0x59, 0x00, 0x58, 0x00, 0x46, 0x00, 0x3d, 0x00, 0x46, 0x00, 0x74, 0x00, 0x51, 0x00, + 0x57, 0x00, 0x6e, 0x00, 0x2d, 0x00, 0x47, 0x00, 0x23, 0x00, 0x45, 0x00, 0x60, 0x00, + 0x4c, 0x00, 0x72, 0x00, 0x4e, 0x00, 0x74, 0x00, 0x40, 0x00, 0x76, 0x00, 0x75, 0x00, + 0x74, 0x00, 0x56, 0x00, 0x44, 0x00, 0x29, 0x00, 0x62, 0x00, 0x58, 0x00, 0x31, 0x00, + 0x78, 0x00, 0x32, 0x00, 0x52, 0x00, 0x4a, 0x00, 0x6b, 0x00, 0x55, 0x00, 0x72, 0x00, + 0x6f, 0x00, 0x6f, 0x00, 0x4a, 0x00, 0x54, 0x00, 0x7d, 0x00, 0x68, 0x00, 0x3f, 0x00, + 0x28, 0x00, 0x21, 0x00, 0x53, 0x00, 0x48, 0x00, 0x5a, 0x00, 0x34, 0x00, 0x36, 0x00, + 0x35, 0x00, 0x64, 0x00, 0x4e, 0x00, 0x75, 0x00, 0x69, 0x00, 0x23, 0x00, 0x75, 0x00, + 0x55, 0x00, 0x43, 0x00, 0x75, 0x00, 0x2f, 0x00, 0x73, 0x00, 0x62, 0x00, 0x6f, 0x00, + 0x37, 0x00, 0x4e, 0x00, 0x25, 0x00, 0x25, 0x00, 0x21, 0x00, 0x3d, 0x00, 0x3c, 0x00, + 0x71, 0x00, 0x3e, 0x00, 0x3f, 0x00, 0x30, 0x00, 0x36, 0x00, 0x62, 0x00, 0x63, 0x00, + 0x53, 0x00, 0x54, 0x00, 0x5d, 0x00, 0x61, 0x00, 0x4c, 0x00, 0x28, 0x00, 0x2b, 0x00, + 0x4c, 0x00, 0x4e, 0x00, 0x66, 0x00, 0x5f, 0x00, 0x4b, 0x00, 0x43, 0x00, 0x75, 0x00, + 0x45, 0x00, 0x37, 0x00, 0x28, 0x00, 0x56, 0x00, 0x36, 0x00, 0x6a, 0x00, 0x3e, 0x00, + 0x64, 0x00, 0x34, 0x00, 0x6a, 0x00, 0x7d, 0x00, 0x4a, 0x00, 0x66, 0x00, 0x7a, 0x00, + 0x3e, 0x00, 0x75, 0x00, 0x38, 0x00, 0x7b, 0x00, 0x42, 0x00, 0x76, 0x00, 0x29, 0x00, + 0x4c, 0x00, 0x65, 0x00, 0x2e, 0x00, 0x32, 0x00, 0x4b, 0x00, 0x2b, 0x00, 0x51, 0x00, + 0x47, 0x00, 0x22, 0x00, 0x48, 0x00, 0x3d, 0x00, 0x49, 0x00, 0x44, 0x00, 0x5d, 0x00, + 0x59, 0x00, 0x63, 0x00, 0x5c, 0x00, 0x24, 0x00, 0x35, 0x00, 0x34, 0x00, 0x70, 0x00, + 0x69, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request, Direction::ToServer) + ); + if let Some(hdr) = dcerpc_state.header { + assert_eq!(0, hdr.hdrtype); + assert_eq!(5, hdr.rpc_vers); + assert_eq!(1024, hdr.frag_length); + } + let tx = &dcerpc_state.transactions[0]; + assert_eq!(11, tx.ctxid); + assert_eq!(9, tx.opnum); + assert_eq!(1, tx.first_request_seen); + assert_eq!(1000, tx.stub_data_buffer_ts.len()); + assert!(tx.stub_data_buffer_reset_ts); + } + + #[test] + pub fn test_parse_bind_pdu() { + let bind1: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x01, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, + 0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let bind2: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x02, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, + 0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x67, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), // TODO ASK if this is correct? + dcerpc_state.handle_input_data(bind2, Direction::ToServer) + ); + } + + #[test] + pub fn test_parse_bind_frag_1() { + let bind1: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc7, 0x70, 0x0d, 0x3e, 0x71, 0x37, 0x39, 0x0d, 0x3a, 0x4f, + 0xd3, 0xdc, 0xca, 0x49, 0xe8, 0xa3, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x84, 0xb6, 0x55, 0x75, 0xdb, 0x9e, 0xba, 0x54, + 0x56, 0xd3, 0x45, 0x10, 0xb7, 0x7a, 0x2a, 0xe2, 0x04, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x6e, 0x39, 0x21, 0x24, 0x70, 0x6f, + 0x41, 0x57, 0x54, 0x70, 0xb8, 0xc3, 0x5e, 0x89, 0x3b, 0x43, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x39, 0x6a, 0x86, 0x5d, + 0x24, 0x0f, 0xd2, 0xf7, 0xb6, 0xce, 0x95, 0x9c, 0x54, 0x1d, 0x3a, 0xdb, 0x02, 0x00, + 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x12, 0xa5, + 0xdd, 0xc5, 0x55, 0xce, 0xc3, 0x46, 0xbd, 0xa0, 0x94, 0x39, 0x3c, 0x0d, 0x9b, 0x5b, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x87, 0x1c, 0x8b, 0x6e, 0x11, 0xa8, 0x67, 0x98, 0xd4, 0x5d, 0xf6, 0x8a, 0x2f, 0x33, + 0x24, 0x7b, 0x05, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x9b, 0x82, 0x13, 0xd1, 0x28, 0xe0, 0x63, 0xf3, 0x62, 0xee, 0x76, 0x73, + 0xf9, 0xac, 0x3d, 0x2e, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x01, 0x00, 0xa9, 0xd4, 0x73, 0xf2, 0xed, 0xad, 0xe8, 0x82, 0xf8, 0xcf, + 0x9d, 0x9f, 0x66, 0xe6, 0x43, 0x37, 0x02, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x06, 0x2b, 0x85, 0x38, 0x4f, 0x73, 0x96, 0xb1, + 0x73, 0xe1, 0x59, 0xbe, 0x9d, 0xe2, 0x6c, 0x07, 0x05, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + ]; + let bind2: &[u8] = &[ + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0xbf, 0xfa, 0xbb, 0xa4, 0x9e, 0x5c, + 0x80, 0x61, 0xb5, 0x8b, 0x79, 0x69, 0xa6, 0x32, 0x88, 0x77, 0x01, 0x00, 0x01, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x39, 0xa8, 0x2c, 0x39, + 0x73, 0x50, 0x06, 0x8d, 0xf2, 0x37, 0x1e, 0x1e, 0xa8, 0x8f, 0x46, 0x98, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x91, 0x13, + 0xd0, 0xa7, 0xef, 0xc4, 0xa7, 0x96, 0x0c, 0x4a, 0x0d, 0x29, 0x80, 0xd3, 0xfe, 0xbf, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, + 0xcc, 0x2b, 0x55, 0x1d, 0xd4, 0xa4, 0x0d, 0xfb, 0xcb, 0x6f, 0x86, 0x36, 0xa6, 0x57, + 0xc3, 0x21, 0x02, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x01, 0x00, 0x43, 0x7b, 0x07, 0xee, 0x85, 0xa8, 0xb9, 0x3a, 0x0f, 0xf9, 0x83, 0x70, + 0xe6, 0x0b, 0x4f, 0x33, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x01, 0x00, 0x9c, 0x6a, 0x15, 0x8c, 0xd6, 0x9c, 0xa6, 0xc3, 0xb2, 0x9e, + 0x62, 0x9f, 0x3d, 0x8e, 0x47, 0x73, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x01, 0x00, 0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01, + 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, 0xe1, 0x88, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind2, Direction::ToServer) + ); + if let Some(ref bind) = dcerpc_state.bind { + assert_eq!(16, bind.numctxitems); + assert_eq!(0, dcerpc_state.bytes_consumed); // because the buffer is cleared after a query is complete + } + } + + #[test] + pub fn test_parse_bind_frag_2() { + let request1: &[u8] = &[ + 0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let request2: &[u8] = &[0x0D, 0x0E]; + let request3: &[u8] = &[0x0F, 0x10, 0x11, 0x12, 0x13, 0x14]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request2, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request3, Direction::ToServer) + ); + let tx = &dcerpc_state.transactions[0]; + assert_eq!(20, tx.stub_data_buffer_ts.len()); + } + + #[test] + pub fn test_parse_bind_frag_3() { + let request1: &[u8] = &[ + 0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + } + + #[test] + pub fn test_parse_bind_frag_4() { + let request1: &[u8] = &[ + 0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + } + + #[test] + pub fn test_parse_dcerpc_frag_1() { + let fault: &[u8] = &[ + 0x05, 0x00, 0x03, 0x03, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xf7, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + let request1: &[u8] = &[0x05, 0x00]; + let request2: &[u8] = &[ + 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::err(), + dcerpc_state.handle_input_data(fault, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request2, Direction::ToServer) + ); + let tx = &dcerpc_state.transactions[0]; + assert_eq!(12, tx.stub_data_buffer_ts.len()); + } + + #[test] + pub fn test_parse_dcerpc_frag_2() { + let request1: &[u8] = &[ + 0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let request2: &[u8] = &[0x05, 0x00]; + let request3: &[u8] = &[ + 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request2, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request3, Direction::ToServer) + ); + } + + #[test] + pub fn test_parse_dcerpc_back_frag() { + let bind_ack1: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0x48, 0x1a, 0x00, 0x00, + ]; + let bind_ack2: &[u8] = &[ + 0x0c, 0x00, 0x5c, 0x50, 0x49, 0x50, 0x45, 0x5c, 0x6c, 0x73, 0x61, 0x73, 0x73, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + dcerpc_state.data_needed_for_dir = Direction::ToClient; + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack1, Direction::ToClient) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack2, Direction::ToClient) + ); + } + + #[test] + // Check if the parser accepts bind pdus that have context ids starting + // from a non-zero value. + pub fn test_parse_bind_pdu_ctx_id_non_zero() { + let bindbuf: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x7f, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + let expected_uuid: &[u8] = &[ + 0x00, 0x00, 0x01, 0xa0, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x46, + ]; + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bindbuf, Direction::ToServer) + ); + if let Some(ref bind) = dcerpc_state.bind { + let bind_uuid = &bind.uuid_list[0].uuid; + assert_eq!(1, bind.uuid_list.len()); + assert_eq!( + cmp::Ordering::Equal, + bind_uuid + .iter() + .zip(expected_uuid) + .map(|(x, y)| x.cmp(y)) + .find(|&ord| ord != cmp::Ordering::Equal) + .unwrap_or_else(|| bind_uuid.len().cmp(&expected_uuid.len())) + ); + } + } + + #[test] + // Check for endless loop with bind PDUs (Imported from C code) + pub fn test_parse_bind_pdu_infinite_loop() { + let bindbuf: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x7f, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0xa0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0xFF, /* ka boom - endless loop */ + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bindbuf, Direction::ToServer) + ); + } + + #[test] + // Check for endless loop with bind_ack PDUs (Imported from C code) + pub fn test_parse_bindack_pdu_infinite_loop() { + let bind_ack: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7f, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0xfd, 0x04, 0x01, 0x00, 0x04, 0x00, 0x31, 0x33, + 0x35, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x01, 0x02, 0x03, 0x04, + 0xFF, + ]; + let mut dcerpc_state = DCERPCState::new(); + dcerpc_state.data_needed_for_dir = Direction::ToClient; + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack, Direction::ToClient) + ); + } + + #[test] + // Check for correct internal ids for bind_acks + pub fn test_parse_bindack_internal_ids() { + let bind1: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x50, 0x08, 0x43, 0x95, 0x43, 0x5a, 0x8b, 0xb2, 0xf4, 0xc5, + 0xb9, 0xee, 0x67, 0x55, 0x7c, 0x19, 0x00, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0xda, 0xc2, 0xbc, 0x9b, 0x35, 0x2e, 0xd4, 0xc9, + 0x1f, 0x85, 0x01, 0xe6, 0x4e, 0x5a, 0x5e, 0xd4, 0x04, 0x00, 0x03, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0xb2, 0x97, 0xcc, 0x14, 0x6f, 0x70, + 0x0d, 0xa5, 0x33, 0xd7, 0xf4, 0xe3, 0x8e, 0xb2, 0x2a, 0x1e, 0x05, 0x00, 0x02, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x96, 0x4e, 0xa6, 0xf6, + 0xb2, 0x4b, 0xae, 0xb3, 0x21, 0xf4, 0x97, 0x7c, 0xcd, 0xa7, 0x08, 0xb0, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0xbc, 0xc0, + 0xf7, 0x71, 0x3f, 0x71, 0x54, 0x44, 0x22, 0xa8, 0x55, 0x0f, 0x98, 0x83, 0x1f, 0xfe, + 0x04, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, + 0xbe, 0x52, 0xf2, 0x58, 0x4a, 0xc3, 0xb5, 0xd0, 0xba, 0xac, 0xda, 0xf0, 0x12, 0x99, + 0x38, 0x6e, 0x04, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0xdb, 0xfa, 0x73, 0x01, 0xb3, 0x81, 0x01, 0xd4, 0x7f, 0xa0, 0x36, 0xb1, + 0x97, 0xae, 0x29, 0x7f, 0x01, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x01, 0x00, 0x89, 0xbe, 0x41, 0x1d, 0x38, 0x75, 0xf5, 0xb5, 0xad, 0x27, + 0x73, 0xf1, 0xb0, 0x7a, 0x28, 0x82, 0x05, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0xf6, 0x87, 0x09, 0x93, 0xb8, 0xa8, 0x20, 0xc4, + 0xb8, 0x63, 0xe6, 0x95, 0xed, 0x59, 0xee, 0x3f, 0x05, 0x00, 0x03, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0x92, 0x77, 0x92, 0x68, 0x3e, 0xa4, + 0xbc, 0x3f, 0x44, 0x33, 0x0e, 0xb8, 0x33, 0x0a, 0x2f, 0xdf, 0x01, 0x00, 0x02, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0xa1, 0x03, 0xd2, 0xa9, + 0xd2, 0x16, 0xc9, 0x89, 0x67, 0x18, 0x3e, 0xb1, 0xee, 0x6b, 0xf9, 0x18, 0x02, 0x00, + 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x2f, 0x09, + 0x5e, 0x74, 0xec, 0xa0, 0xbb, 0xc1, 0x60, 0x18, 0xf1, 0x93, 0x04, 0x17, 0x11, 0xf9, + 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, + 0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01, 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, + 0xe1, 0x88, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let bind_ack1: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0xc1, 0x2b, 0x00, 0x00, 0x0e, 0x00, 0x5c, 0x50, + 0x49, 0x50, 0x45, 0x5c, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x00, 0x0d, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let bind2: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0xdc, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xc7, 0x70, 0x0d, 0x3e, 0x71, 0x37, 0x39, 0x0d, 0x3a, 0x4f, + 0xd3, 0xdc, 0xca, 0x49, 0xe8, 0xa3, 0x05, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x84, 0xb6, 0x55, 0x75, 0xdb, 0x9e, 0xba, 0x54, + 0x56, 0xd3, 0x45, 0x10, 0xb7, 0x7a, 0x2a, 0xe2, 0x04, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x6e, 0x39, 0x21, 0x24, 0x70, 0x6f, + 0x41, 0x57, 0x54, 0x70, 0xb8, 0xc3, 0x5e, 0x89, 0x3b, 0x43, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0x39, 0x6a, 0x86, 0x5d, + 0x24, 0x0f, 0xd2, 0xf7, 0xb6, 0xce, 0x95, 0x9c, 0x54, 0x1d, 0x3a, 0xdb, 0x02, 0x00, + 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x12, 0xa5, + 0xdd, 0xc5, 0x55, 0xce, 0xc3, 0x46, 0xbd, 0xa0, 0x94, 0x39, 0x3c, 0x0d, 0x9b, 0x5b, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x87, 0x1c, 0x8b, 0x6e, 0x11, 0xa8, 0x67, 0x98, 0xd4, 0x5d, 0xf6, 0x8a, 0x2f, 0x33, + 0x24, 0x7b, 0x05, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x9b, 0x82, 0x13, 0xd1, 0x28, 0xe0, 0x63, 0xf3, 0x62, 0xee, 0x76, 0x73, + 0xf9, 0xac, 0x3d, 0x2e, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x01, 0x00, 0xa9, 0xd4, 0x73, 0xf2, 0xed, 0xad, 0xe8, 0x82, 0xf8, 0xcf, + 0x9d, 0x9f, 0x66, 0xe6, 0x43, 0x37, 0x02, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x06, 0x2b, 0x85, 0x38, 0x4f, 0x73, 0x96, 0xb1, + 0x73, 0xe1, 0x59, 0xbe, 0x9d, 0xe2, 0x6c, 0x07, 0x05, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0xbf, 0xfa, 0xbb, 0xa4, 0x9e, 0x5c, + 0x80, 0x61, 0xb5, 0x8b, 0x79, 0x69, 0xa6, 0x32, 0x88, 0x77, 0x01, 0x00, 0x01, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x39, 0xa8, 0x2c, 0x39, + 0x73, 0x50, 0x06, 0x8d, 0xf2, 0x37, 0x1e, 0x1e, 0xa8, 0x8f, 0x46, 0x98, 0x02, 0x00, + 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x91, 0x13, + 0xd0, 0xa7, 0xef, 0xc4, 0xa7, 0x96, 0x0c, 0x4a, 0x0d, 0x29, 0x80, 0xd3, 0xfe, 0xbf, + 0x00, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x01, 0x00, + 0xcc, 0x2b, 0x55, 0x1d, 0xd4, 0xa4, 0x0d, 0xfb, 0xcb, 0x6f, 0x86, 0x36, 0xa6, 0x57, + 0xc3, 0x21, 0x02, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0d, 0x00, + 0x01, 0x00, 0x43, 0x7b, 0x07, 0xee, 0x85, 0xa8, 0xb9, 0x3a, 0x0f, 0xf9, 0x83, 0x70, + 0xe6, 0x0b, 0x4f, 0x33, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x0e, 0x00, 0x01, 0x00, 0x9c, 0x6a, 0x15, 0x8c, 0xd6, 0x9c, 0xa6, 0xc3, 0xb2, 0x9e, + 0x62, 0x9f, 0x3d, 0x8e, 0x47, 0x73, 0x02, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x0f, 0x00, 0x01, 0x00, 0xc8, 0x4f, 0x32, 0x4b, 0x70, 0x16, 0xd3, 0x01, + 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, 0xe1, 0x88, 0x03, 0x00, 0x00, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, + ]; + let bind_ack2: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0xac, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0xc2, 0x2b, 0x00, 0x00, 0x0e, 0x00, 0x5c, 0x50, + 0x49, 0x50, 0x45, 0x5c, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let bind3: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x2c, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0xa4, 0x7f, 0x8e, 0xc6, 0xef, 0x56, 0x9b, 0x63, 0x92, 0xfa, + 0x08, 0xb3, 0x35, 0xe2, 0xa5, 0x81, 0x00, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x9f, 0xfc, 0x78, 0xd2, 0x5f, 0x16, 0x0b, 0xbc, + 0xc6, 0xdb, 0x5d, 0xef, 0xde, 0x54, 0xa2, 0x6f, 0x04, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x78, 0xb8, 0x96, 0xc7, 0x2f, 0xda, + 0x11, 0x6b, 0xd1, 0x28, 0x68, 0xe1, 0xd6, 0x71, 0xac, 0x9d, 0x03, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x01, 0x00, 0xcf, 0xf4, 0xd7, 0x37, + 0x03, 0xda, 0xcc, 0xe3, 0x3e, 0x34, 0x7f, 0x67, 0x99, 0x91, 0x41, 0x3d, 0x01, 0x00, + 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x48, 0xeb, + 0x32, 0xf0, 0x27, 0xd5, 0x9d, 0xd0, 0x1e, 0xc6, 0x48, 0x46, 0x97, 0xe9, 0xdb, 0x09, + 0x05, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, + 0x82, 0xec, 0x0d, 0x08, 0xf2, 0x8f, 0x22, 0x57, 0x42, 0x9b, 0xce, 0xa8, 0x74, 0x16, + 0xc6, 0xec, 0x00, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x2e, 0x00, 0x70, 0x44, 0xee, 0xc9, 0x30, 0x6b, 0xf4, 0x34, 0x1e, 0x3d, + 0x35, 0x0f, 0xf7, 0xf7, 0x00, 0x00, 0x01, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x01, 0x00, 0x59, 0x04, 0x39, 0x3f, 0x59, 0x87, 0x14, 0x0e, 0x76, 0x8d, + 0x17, 0xc2, 0x47, 0xfa, 0x67, 0x7f, 0x04, 0x00, 0x02, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x01, 0x00, 0x30, 0xd6, 0xed, 0x2e, 0x57, 0xfa, 0xf4, 0x72, + 0x6c, 0x10, 0x0d, 0xe5, 0x51, 0x7f, 0xd0, 0x39, 0x02, 0x00, 0x01, 0x00, 0x04, 0x5d, + 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, + 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x01, 0x00, 0xea, 0x8b, 0x84, 0x4d, 0x44, 0x43, + 0xc1, 0x94, 0x75, 0xe2, 0x81, 0x48, 0xd8, 0x77, 0xd9, 0xce, 0x05, 0x00, 0x00, 0x00, + 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, + 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x89, 0x4f, 0xe7, 0x95, + 0xa3, 0xc1, 0x62, 0x36, 0x26, 0x9e, 0x67, 0xdb, 0x2c, 0x52, 0x89, 0xd3, 0x01, 0x00, + 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, + 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x01, 0x00, 0x78, 0x56, + 0x34, 0x12, 0x34, 0x12, 0xcd, 0xab, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, + 0x01, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let bind_ack3: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0x4c, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0x1a, 0x33, 0x00, 0x00, 0x0e, 0x00, 0x5c, 0x70, + 0x69, 0x70, 0x65, 0x5c, 0x73, 0x70, 0x6f, 0x6f, 0x6c, 0x73, 0x73, 0x00, 0x0c, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, + 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let mut dcerpc_state = DCERPCState::new(); + let expected_uuid1 = vec![ + 0x4b, 0x32, 0x4f, 0xc8, 0x16, 0x70, 0x01, 0xd3, 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, + 0xe1, 0x88, + ]; + let expected_uuid2 = vec![ + 0x4b, 0x32, 0x4f, 0xc8, 0x16, 0x70, 0x01, 0xd3, 0x12, 0x78, 0x5a, 0x47, 0xbf, 0x6e, + 0xe1, 0x88, + ]; + let expected_uuid3 = vec![ + 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0xab, 0xcd, 0xef, 0x00, 0x01, 0x23, 0x45, 0x67, + 0x89, 0xab, + ]; + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack1, Direction::ToClient) + ); + if let Some(ref back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + assert_eq!(12, back.accepted_uuid_list[0].ctxid); + assert_eq!(expected_uuid1, back.accepted_uuid_list[0].uuid); + } + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind2, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack2, Direction::ToClient) + ); + if let Some(ref back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + assert_eq!(15, back.accepted_uuid_list[0].ctxid); + assert_eq!(expected_uuid2, back.accepted_uuid_list[0].uuid); + } + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind3, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind_ack3, Direction::ToClient) + ); + if let Some(ref back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + dcerpc_state.data_needed_for_dir = Direction::ToServer; + assert_eq!(11, back.accepted_uuid_list[0].ctxid); + assert_eq!(expected_uuid3, back.accepted_uuid_list[0].uuid); + } + } + + #[test] + pub fn test_bind_acks_alter_contexts_internal_ids() { + let bind: &[u8] = &[ + 0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x40, 0xfd, 0x2c, 0x34, 0x6c, 0x3c, 0xce, 0x11, 0xa8, 0x93, + 0x08, 0x00, 0x2b, 0x2e, 0x9c, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let bindack: &[u8] = &[ + 0x05, 0x00, 0x0c, 0x03, 0x10, 0x00, 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0x7d, 0xd8, 0x00, 0x00, 0x0d, 0x00, 0x5c, 0x70, + 0x69, 0x70, 0x65, 0x5c, 0x6c, 0x6c, 0x73, 0x72, 0x70, 0x63, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11, + 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + let alter_context: &[u8] = &[ + 0x05, 0x00, 0x0e, 0x03, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0xd0, 0x4c, 0x67, 0x57, 0x00, 0x52, 0xce, 0x11, 0xa8, 0x97, + 0x08, 0x00, 0x2b, 0x2e, 0x9c, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let alter_context_resp: &[u8] = &[ + 0x05, 0x00, 0x0f, 0x03, 0x10, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10, 0x7d, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, + 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, 0x00, 0x00, + ]; + + let mut dcerpc_state = DCERPCState::new(); + let expected_uuid1 = vec![ + 0x34, 0x2c, 0xfd, 0x40, 0x3c, 0x6c, 0x11, 0xce, 0xa8, 0x93, 0x08, 0x00, 0x2b, 0x2e, + 0x9c, 0x6d, + ]; + let expected_uuid2 = vec![ + 0x57, 0x67, 0x4c, 0xd0, 0x52, 0x00, 0x11, 0xce, 0xa8, 0x97, 0x08, 0x00, 0x2b, 0x2e, + 0x9c, 0x6d, + ]; + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bind, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(bindack, Direction::ToClient) + ); + if let Some(ref back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + assert_eq!(0, back.accepted_uuid_list[0].ctxid); + assert_eq!(expected_uuid1, back.accepted_uuid_list[0].uuid); + } + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(alter_context, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(alter_context_resp, Direction::ToClient) + ); + if let Some(ref back) = dcerpc_state.bindack { + assert_eq!(1, back.accepted_uuid_list.len()); + assert_eq!(1, back.accepted_uuid_list[0].ctxid); + assert_eq!(expected_uuid2, back.accepted_uuid_list[0].uuid); + } + } + + #[test] + pub fn test_parse_dcerpc_frag_3() { + let request1: &[u8] = &[ + 0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x0c, 0x00, + ]; + let request2: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0xFF, 0xFF, + ]; + let mut dcerpc_state = DCERPCState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request1, Direction::ToServer) + ); + assert_eq!( + AppLayerResult::ok(), + dcerpc_state.handle_input_data(request2, Direction::ToServer) + ); + let tx = &dcerpc_state.transactions[0]; + assert_eq!(2, tx.opnum); + assert_eq!(0, tx.ctxid); + assert_eq!(14, tx.stub_data_buffer_ts.len()); + } +} diff --git a/rust/src/dcerpc/dcerpc_udp.rs b/rust/src/dcerpc/dcerpc_udp.rs new file mode 100644 index 0000000..83707bd --- /dev/null +++ b/rust/src/dcerpc/dcerpc_udp.rs @@ -0,0 +1,580 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::applayer::{self, *}; +use crate::core::{self, Direction, DIR_BOTH}; +use crate::dcerpc::dcerpc::{ + DCERPCTransaction, DCERPC_MAX_TX, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, PFCL1_FRAG, PFCL1_LASTFRAG, + rs_dcerpc_get_alstate_progress, ALPROTO_DCERPC, PARSER_NAME, +}; +use nom7::Err; +use std; +use std::ffi::CString; +use std::collections::VecDeque; +use crate::dcerpc::parser; + +// Constant DCERPC UDP Header length +pub const DCERPC_UDP_HDR_LEN: i32 = 80; + +#[derive(Default, Debug)] +pub struct DCERPCHdrUdp { + pub rpc_vers: u8, + pub pkt_type: u8, + pub flags1: u8, + pub flags2: u8, + pub drep: Vec<u8>, + pub serial_hi: u8, + pub objectuuid: Vec<u8>, + pub interfaceuuid: Vec<u8>, + pub activityuuid: Vec<u8>, + pub server_boot: u32, + pub if_vers: u32, + pub seqnum: u32, + pub opnum: u16, + pub ihint: u16, + pub ahint: u16, + pub fraglen: u16, + pub fragnum: u16, + pub auth_proto: u8, + pub serial_lo: u8, +} + +#[derive(Default, Debug)] +pub struct DCERPCUDPState { + state_data: AppLayerStateData, + pub tx_id: u64, + pub transactions: VecDeque<DCERPCTransaction>, + tx_index_completed: usize, +} + +impl State<DCERPCTransaction> for DCERPCUDPState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&DCERPCTransaction> { + self.transactions.get(index) + } +} + +impl DCERPCUDPState { + pub fn new() -> Self { + Default::default() + } + + fn create_tx(&mut self, hdr: &DCERPCHdrUdp) -> DCERPCTransaction { + let mut tx = DCERPCTransaction::new(); + tx.id = self.tx_id; + tx.endianness = hdr.drep[0] & 0x10; + tx.activityuuid = hdr.activityuuid.to_vec(); + tx.seqnum = hdr.seqnum; + self.tx_id += 1; + if self.transactions.len() > unsafe { DCERPC_MAX_TX } { + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if !tx_old.req_done || !tx_old.resp_done { + tx_old.req_done = true; + tx_old.resp_done = true; + break; + } + } + self.tx_index_completed = index; + } + tx + } + + pub fn free_tx(&mut self, tx_id: u64) { + SCLogDebug!("Freeing TX with ID {} TX.ID {}", tx_id, tx_id+1); + 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; + SCLogDebug!("tx {} progress {}/{}", tx.id, tx.req_done, tx.resp_done); + break; + } + } + if found { + SCLogDebug!("freeing TX with ID {} TX.ID {} at index {} left: {} max id: {}", + tx_id, tx_id+1, index, self.transactions.len(), self.tx_id); + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + /// Get transaction as per the given transaction ID. Transaction ID with + /// which the lookup is supposed to be done as per the calls from AppLayer + /// parser in C. This requires an internal transaction ID to be maintained. + /// + /// Arguments: + /// * `tx_id`: + /// description: internal transaction ID to track transactions + /// + /// Return value: + /// Option mutable reference to DCERPCTransaction + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut DCERPCTransaction> { + for tx in &mut self.transactions { + let found = tx.id == tx_id; + if found { + return Some(tx); + } + } + None + } + + fn find_incomplete_tx(&mut self, hdr: &DCERPCHdrUdp) -> Option<&mut DCERPCTransaction> { + for tx in &mut self.transactions { + if tx.seqnum == hdr.seqnum && tx.activityuuid == hdr.activityuuid && ((hdr.pkt_type == DCERPC_TYPE_REQUEST && !tx.req_done) || (hdr.pkt_type == DCERPC_TYPE_RESPONSE && !tx.resp_done)) { + SCLogDebug!("found tx id {}, last tx_id {}, {} {}", tx.id, self.tx_id, tx.seqnum, tx.activityuuid[0]); + return Some(tx); + } + } + None + } + + pub fn handle_fragment_data(&mut self, hdr: &DCERPCHdrUdp, input: &[u8]) -> bool { + if hdr.pkt_type != DCERPC_TYPE_REQUEST && hdr.pkt_type != DCERPC_TYPE_RESPONSE { + SCLogDebug!("Unrecognized packet type"); + return false; + } + + let mut otx = self.find_incomplete_tx(hdr); + if otx.is_none() { + let ntx = self.create_tx(hdr); + SCLogDebug!("new tx id {}, last tx_id {}, {} {}", ntx.id, self.tx_id, ntx.seqnum, ntx.activityuuid[0]); + self.transactions.push_back(ntx); + otx = self.transactions.back_mut(); + } + + if let Some(tx) = otx { + let done = (hdr.flags1 & PFCL1_FRAG) == 0 || (hdr.flags1 & PFCL1_LASTFRAG) != 0; + + match hdr.pkt_type { + DCERPC_TYPE_REQUEST => { + tx.stub_data_buffer_ts.extend_from_slice(input); + tx.frag_cnt_ts += 1; + if done { + tx.req_done = true; + } + return true; + } + DCERPC_TYPE_RESPONSE => { + tx.stub_data_buffer_tc.extend_from_slice(input); + tx.frag_cnt_tc += 1; + if done { + tx.resp_done = true; + } + return true; + } + _ => { + // unreachable + } + } + } + return false; // unreachable + } + + pub fn handle_input_data(&mut self, input: &[u8]) -> AppLayerResult { + // Input length should at least be header length + if (input.len() as i32) < DCERPC_UDP_HDR_LEN { + return AppLayerResult::err(); + } + + // Call header parser first + match parser::parse_dcerpc_udp_header(input) { + Ok((leftover_bytes, header)) => { + if header.rpc_vers != 4 { + SCLogDebug!("DCERPC UDP Header did not validate."); + return AppLayerResult::err(); + } + if leftover_bytes.len() < header.fraglen as usize { + SCLogDebug!("Insufficient data: leftover_bytes {}, fraglen {}", leftover_bytes.len(), header.fraglen); + return AppLayerResult::err(); + } + if !self.handle_fragment_data(&header, &leftover_bytes[..header.fraglen as usize]) { + return AppLayerResult::err(); + } + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DCERPC request"); + return AppLayerResult::err(); + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DCERPC request"); + return AppLayerResult::err(); + } + } + return AppLayerResult::ok(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_parse( + _flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DCERPCUDPState); + if !stream_slice.is_gap() { + return state.handle_input_data(stream_slice.as_slice()); + } + AppLayerResult::err() +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_udp_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut DCERPCUDPState) }); +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_udp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: core::AppProto) -> *mut std::os::raw::c_void { + let state = DCERPCUDPState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_state_transaction_free( + state: *mut std::os::raw::c_void, tx_id: u64, +) { + let dce_state = cast_pointer!(state, DCERPCUDPState); + SCLogDebug!("freeing tx {}", tx_id); + dce_state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, DCERPCTransaction); + return &mut tx.tx_data; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let dce_state = cast_pointer!(state, DCERPCUDPState); + match dce_state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + }, + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_get_tx_cnt(vtx: *mut std::os::raw::c_void) -> u64 { + let dce_state = cast_pointer!(vtx, DCERPCUDPState); + dce_state.tx_id +} + +/// Probe input to see if it looks like DCERPC. +fn probe(input: &[u8]) -> (bool, bool) { + match parser::parse_dcerpc_udp_header(input) { + Ok((_, hdr)) => { + let is_request = hdr.pkt_type == 0x00; + let is_dcerpc = hdr.rpc_vers == 0x04 && + (hdr.flags2 & 0xfc == 0) && + (hdr.drep[0] & 0xee == 0) && + (hdr.drep[1] <= 3); + return (is_dcerpc, is_request); + }, + Err(_) => (false, false), + } +} + +pub unsafe extern "C" fn rs_dcerpc_probe_udp(_f: *const core::Flow, direction: u8, input: *const u8, + len: u32, rdir: *mut u8) -> core::AppProto +{ + SCLogDebug!("Probing the packet for DCERPC/UDP"); + if len == 0 { + return core::ALPROTO_UNKNOWN; + } + let slice: &[u8] = std::slice::from_raw_parts(input as *mut u8, len as usize); + //is_incomplete is checked by caller + let (is_dcerpc, is_request) = probe(slice); + if is_dcerpc { + let dir: Direction = (direction & DIR_BOTH).into(); + if is_request { + if dir != Direction::ToServer { + *rdir = Direction::ToServer.into(); + } + } else if dir != Direction::ToClient { + *rdir = Direction::ToClient.into(); + }; + return ALPROTO_DCERPC; + } + return core::ALPROTO_FAILED; +} + +fn register_pattern_probe() -> i8 { + unsafe { + if AppLayerProtoDetectPMRegisterPatternCSwPP(core::IPPROTO_UDP, ALPROTO_DCERPC, + b"|04 00|\0".as_ptr() as *const std::os::raw::c_char, 2, 0, + Direction::ToServer.into(), rs_dcerpc_probe_udp, 0, 0) < 0 { + SCLogDebug!("TOSERVER => AppLayerProtoDetectPMRegisterPatternCSwPP FAILED"); + return -1; + } + } + 0 +} + +export_state_data_get!(rs_dcerpc_udp_get_state_data, DCERPCUDPState); + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_udp_register_parser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: core::IPPROTO_UDP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_dcerpc_udp_state_new, + state_free: rs_dcerpc_udp_state_free, + tx_free: rs_dcerpc_udp_state_transaction_free, + parse_ts: rs_dcerpc_udp_parse, + parse_tc: rs_dcerpc_udp_parse, + get_tx_count: rs_dcerpc_udp_get_tx_cnt, + get_tx: rs_dcerpc_udp_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_dcerpc_get_alstate_progress, + get_eventinfo: None, + get_eventinfo_byid: None, + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<DCERPCUDPState, DCERPCTransaction>), + get_tx_data: rs_dcerpc_udp_get_tx_data, + get_state_data: rs_dcerpc_udp_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DCERPC = alproto; + if register_pattern_probe() < 0 { + return; + } + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detecter and parser disabled for DCERPC/UDP."); + } +} + + +#[cfg(test)] +mod tests { + use crate::applayer::AppLayerResult; + use crate::dcerpc::dcerpc_udp::DCERPCUDPState; + use crate::dcerpc::parser; + + #[test] + fn test_process_header_udp_incomplete_hdr() { + let request: &[u8] = &[ + 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, + 0x1c, 0x7d, 0xcf, 0x11, + ]; + + match parser::parse_dcerpc_udp_header(request) { + Ok((_rem, _header)) => { + { assert!(false); } + } + _ => {} + } + } + + #[test] + fn test_process_header_udp_perfect_hdr() { + let request: &[u8] = &[ + 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, + 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, 0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57, 0x86, 0xc2, + 0x37, 0x67, 0xf7, 0x1e, 0xd1, 0x11, 0xbc, 0xd9, 0x00, 0x60, 0x97, 0x92, 0xd2, 0x6c, + 0x79, 0xbe, 0x01, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x68, 0x00, 0x00, 0x00, 0x0a, 0x00, + ]; + match parser::parse_dcerpc_udp_header(request) { + Ok((rem, header)) => { + assert_eq!(4, header.rpc_vers); + assert_eq!(80, request.len() - rem.len()); + } + _ => { assert!(false); } + } + } + + #[test] + fn test_handle_fragment_data_udp_no_body() { + let request: &[u8] = &[ + 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, + 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, 0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57, 0x86, 0xc2, + 0x37, 0x67, 0xf7, 0x1e, 0xd1, 0x11, 0xbc, 0xd9, 0x00, 0x60, 0x97, 0x92, 0xd2, 0x6c, + 0x79, 0xbe, 0x01, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x68, 0x00, 0x00, 0x00, 0x0a, 0x00, + ]; + match parser::parse_dcerpc_udp_header(request) { + Ok((rem, header)) => { + assert_eq!(4, header.rpc_vers); + assert_eq!(80, request.len() - rem.len()); + assert_eq!(0, rem.len()); + } + _ => { assert!(false); } + } + } + + #[test] + fn test_handle_input_data_udp_full_body() { + let request: &[u8] = &[ + 0x04, 0x00, 0x2c, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x3f, 0x98, + 0xf0, 0x5c, 0xd9, 0x63, 0xcc, 0x46, 0xc2, 0x74, 0x51, 0x6c, 0x8a, 0x53, 0x7d, 0x6f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x70, 0x05, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x24, 0x58, 0xfd, 0xcc, 0x45, + 0x64, 0x49, 0xb0, 0x70, 0xdd, 0xae, 0x74, 0x2c, 0x96, 0xd2, 0x60, 0x5e, 0x0d, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x5e, 0x0d, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x7c, 0x5e, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x80, 0x96, 0xf1, 0xf1, 0x2a, 0x4d, 0xce, 0x11, 0xa6, 0x6a, 0x00, 0x20, 0xaf, 0x6e, + 0x72, 0xf4, 0x0c, 0x00, 0x00, 0x00, 0x4d, 0x41, 0x52, 0x42, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x0d, 0xf0, 0xad, 0xba, 0x00, 0x00, 0x00, 0x00, 0xa8, 0xf4, + 0x0b, 0x00, 0x10, 0x09, 0x00, 0x00, 0x10, 0x09, 0x00, 0x00, 0x4d, 0x45, 0x4f, 0x57, + 0x04, 0x00, 0x00, 0x00, 0xa2, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x38, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x08, + 0x00, 0x00, 0xd8, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, + 0xcc, 0xcc, 0xcc, 0xcc, 0xc8, 0x00, 0x00, 0x00, 0x4d, 0x45, 0x4f, 0x57, 0xd8, 0x08, + 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, + 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x28, 0xcd, 0x00, 0x64, 0x29, 0xcd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0xb9, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xab, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xa5, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0xa6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x46, 0xa4, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x46, 0xad, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0xaa, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x07, 0x00, 0x00, 0x00, 0x60, 0x00, + 0x00, 0x00, 0x58, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x28, 0x06, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x50, 0x00, 0x00, 0x00, + 0x4f, 0xb6, 0x88, 0x20, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x48, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x66, 0x00, 0x06, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x46, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x19, 0x0c, 0x00, + 0x58, 0x00, 0x00, 0x00, 0x05, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00, 0x70, 0xd8, + 0x98, 0x93, 0x98, 0x4f, 0xd2, 0x11, 0xa9, 0x3d, 0xbe, 0x57, 0xb2, 0x00, 0x00, 0x00, + 0x32, 0x00, 0x31, 0x00, 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x80, 0x00, + 0x00, 0x00, 0x0d, 0xf0, 0xad, 0xba, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x43, 0x14, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x4d, 0x45, 0x4f, 0x57, + 0x04, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x3b, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x81, 0xc5, 0x17, 0x03, 0x80, 0x0e, 0xe9, 0x4a, + 0x99, 0x99, 0xf1, 0x8a, 0x50, 0x6f, 0x7a, 0x85, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, + 0xcc, 0xcc, 0x30, 0x00, 0x00, 0x00, 0x78, 0x00, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xd8, 0xda, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x2f, + 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x00, 0x58, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, 0x10, 0x00, 0x00, 0x00, + 0x30, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x10, 0x08, 0x00, 0xcc, 0xcc, 0xcc, 0xcc, + 0x68, 0x00, 0x00, 0x00, 0x0e, 0x00, 0xff, 0xff, 0x68, 0x8b, 0x0b, 0x00, 0x02, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00, 0x5c, 0x00, 0x5c, 0x00, 0x31, 0x00, + 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, + 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, + 0x31, 0x00, 0x31, 0x00, 0x31, 0x00, 0x9d, 0x13, 0x00, 0x01, 0xcc, 0xe0, 0xfd, 0x7f, + 0xcc, 0xe0, 0xfd, 0x7f, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, + ]; + let mut dcerpcudp_state = DCERPCUDPState::new(); + assert_eq!( + AppLayerResult::ok(), + dcerpcudp_state.handle_input_data(request) + ); + assert_eq!( + 1392, + dcerpcudp_state.transactions[0].stub_data_buffer_ts.len() + ); + } +} diff --git a/rust/src/dcerpc/detect.rs b/rust/src/dcerpc/detect.rs new file mode 100644 index 0000000..81f2854 --- /dev/null +++ b/rust/src/dcerpc/detect.rs @@ -0,0 +1,483 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::dcerpc::{ + DCERPCState, DCERPCTransaction, DCERPC_TYPE_REQUEST, DCERPC_TYPE_RESPONSE, + DCERPC_UUID_ENTRY_FLAG_FF, +}; +use crate::detect::uint::{detect_match_uint, detect_parse_uint, DetectUintData}; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; +use uuid::Uuid; + +pub const DETECT_DCE_OPNUM_RANGE_UNINITIALIZED: u32 = 100000; + +#[derive(Debug)] +pub struct DCEIfaceData { + pub if_uuid: Vec<u8>, + pub du16: Option<DetectUintData<u16>>, + pub any_frag: u8, +} + +#[derive(Debug)] +pub struct DCEOpnumRange { + pub range1: u32, + pub range2: u32, +} + +impl Default for DCEOpnumRange { + fn default() -> Self { + Self::new() + } +} + +impl DCEOpnumRange { + pub fn new() -> Self { + Self { + range1: DETECT_DCE_OPNUM_RANGE_UNINITIALIZED, + range2: DETECT_DCE_OPNUM_RANGE_UNINITIALIZED, + } + } +} + +#[derive(Debug)] +pub struct DCEOpnumData { + pub data: Vec<DCEOpnumRange>, +} + +fn match_backuuid( + tx: &mut DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData, +) -> u8 { + let mut ret = 0; + if let Some(ref bindack) = state.bindack { + for uuidentry in bindack.accepted_uuid_list.iter() { + ret = 1; + // if any_frag is not enabled, we need to match only against the first fragment + if if_data.any_frag == 0 && (uuidentry.flags & DCERPC_UUID_ENTRY_FLAG_FF == 0) { + SCLogDebug!("any frag not enabled"); + continue; + } + // if the uuid has been rejected(uuidentry->result == 1), we skip to the next uuid + if uuidentry.result != 0 { + SCLogDebug!("Skipping to next UUID"); + continue; + } + + for i in 0..16 { + if if_data.if_uuid[i] != uuidentry.uuid[i] { + SCLogDebug!("Iface UUID and BINDACK Accepted UUID does not match"); + ret = 0; + break; + } + } + let ctxid = tx.get_req_ctxid(); + ret &= (uuidentry.ctxid == ctxid) as u8; + if ret == 0 { + SCLogDebug!("CTX IDs/UUIDs do not match"); + continue; + } + + if let Some(x) = &if_data.du16 { + if !detect_match_uint(x, uuidentry.version) { + SCLogDebug!("Interface version did not match"); + ret &= 0; + } + } + + if ret == 1 { + return 1; + } + } + } + + return ret; +} + +fn parse_iface_data(arg: &str) -> Result<DCEIfaceData, ()> { + let split_args: Vec<&str> = arg.split(',').collect(); + let mut du16 = None; + let mut any_frag: u8 = 0; + let if_uuid = match Uuid::parse_str(split_args[0]) { + Ok(res) => res.as_bytes().to_vec(), + _ => { + return Err(()); + } + }; + + match split_args.len() { + 1 => {} + 2 => match split_args[1] { + "any_frag" => { + any_frag = 1; + } + _ => { + match detect_parse_uint(split_args[1]) { + Ok((_, x)) => du16 = Some(x), + _ => { + return Err(()); + } + }; + } + }, + 3 => { + match detect_parse_uint(split_args[1]) { + Ok((_, x)) => du16 = Some(x), + _ => { + return Err(()); + } + }; + if split_args[2] != "any_frag" { + return Err(()); + } + any_frag = 1; + } + _ => { + return Err(()); + } + } + + Ok(DCEIfaceData { + if_uuid, + du16, + any_frag, + }) +} + +fn convert_str_to_u32(arg: &str) -> Result<u32, ()> { + match arg.parse::<u32>() { + Ok(res) => Ok(res), + _ => Err(()), + } +} + +fn parse_opnum_data(arg: &str) -> Result<DCEOpnumData, ()> { + let split_args: Vec<&str> = arg.split(',').collect(); + let mut dce_opnum_data: Vec<DCEOpnumRange> = Vec::new(); + for range in split_args.iter() { + let mut opnum_range = DCEOpnumRange::new(); + let split_range: Vec<&str> = range.split('-').collect(); + let split_len = split_range.len(); + + if (split_len > 0 && convert_str_to_u32(split_range[0]).is_err()) + || (split_len > 1 && convert_str_to_u32(split_range[1]).is_err()) + { + return Err(()); + } + match split_len { + 1 => { + opnum_range.range1 = convert_str_to_u32(split_range[0]).unwrap(); + } + 2 => { + let range1 = convert_str_to_u32(split_range[0]).unwrap(); + let range2 = convert_str_to_u32(split_range[1]).unwrap(); + if range2 < range1 { + return Err(()); + } + opnum_range.range1 = range1; + opnum_range.range2 = range2; + } + _ => { + return Err(()); + } + } + dce_opnum_data.push(opnum_range); + } + + Ok(DCEOpnumData { + data: dce_opnum_data, + }) +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_iface_match( + tx: &mut DCERPCTransaction, state: &mut DCERPCState, if_data: &mut DCEIfaceData, +) -> u8 { + let first_req_seen = tx.get_first_req_seen(); + if first_req_seen == 0 { + return 0; + } + + match state.get_hdr_type() { + Some(x) => match x { + DCERPC_TYPE_REQUEST | DCERPC_TYPE_RESPONSE => {} + _ => { + return 0; + } + }, + None => { + return 0; + } + }; + + return match_backuuid(tx, state, if_data); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_iface_parse(carg: *const c_char) -> *mut c_void { + if carg.is_null() { + return std::ptr::null_mut(); + } + let arg = match CStr::from_ptr(carg).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match parse_iface_data(arg) { + Ok(detect) => Box::into_raw(Box::new(detect)) as *mut _, + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_iface_free(ptr: *mut c_void) { + if !ptr.is_null() { + std::mem::drop(Box::from_raw(ptr as *mut DCEIfaceData)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_opnum_match( + tx: &mut DCERPCTransaction, opnum_data: &mut DCEOpnumData, +) -> u8 { + let first_req_seen = tx.get_first_req_seen(); + if first_req_seen == 0 { + return 0; + } + let opnum = tx.get_req_opnum(); + for range in opnum_data.data.iter() { + if range.range2 == DETECT_DCE_OPNUM_RANGE_UNINITIALIZED { + if range.range1 == opnum as u32 { + return 1; + } + } else if range.range1 <= opnum as u32 && range.range2 >= opnum as u32 { + return 1; + } + } + + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_opnum_parse(carg: *const c_char) -> *mut c_void { + if carg.is_null() { + return std::ptr::null_mut(); + } + let arg = match CStr::from_ptr(carg).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match parse_opnum_data(arg) { + Ok(detect) => Box::into_raw(Box::new(detect)) as *mut _, + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dcerpc_opnum_free(ptr: *mut c_void) { + if !ptr.is_null() { + std::mem::drop(Box::from_raw(ptr as *mut DCEOpnumData)); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::detect::uint::DetectUintMode; + + fn extract_op_version(i: &str) -> Result<(DetectUintMode, u16), ()> { + match detect_parse_uint(i) { + Ok((_, d)) => return Ok((d.mode, d.arg1)), + _ => { + return Err(()); + } + } + } + #[test] + fn test_extract_op_version() { + let op_version = "<1"; + assert_eq!( + Ok((DetectUintMode::DetectUintModeLt, 1)), + extract_op_version(op_version) + ); + + let op_version = ">10"; + assert_eq!( + Ok((DetectUintMode::DetectUintModeGt, 10)), + extract_op_version(op_version) + ); + + let op_version = "=45"; + assert_eq!( + Ok((DetectUintMode::DetectUintModeEqual, 45)), + extract_op_version(op_version) + ); + + let op_version = "!0"; + assert_eq!( + Ok((DetectUintMode::DetectUintModeNe, 0)), + extract_op_version(op_version) + ); + + let op_version = "@1"; + assert!(extract_op_version(op_version).is_err()); + + let op_version = ""; + assert_eq!(Err(()), extract_op_version(op_version)); + } + + #[test] + fn test_match_iface_version() { + let iface_data = DetectUintData::<u16> { + mode: DetectUintMode::DetectUintModeEqual, + arg1: 10, + arg2: 0, + }; + let version: u16 = 10; + assert!(detect_match_uint(&iface_data, version)); + + let version: u16 = 2; + assert!(!detect_match_uint(&iface_data, version)); + } + + #[test] + fn test_parse_iface_data() { + let arg = "12345678-1234-1234-1234-123456789ABC"; + let iface_data = parse_iface_data(arg).unwrap(); + let expected_uuid = Ok(String::from("12345678-1234-1234-1234-123456789ABC").to_lowercase()); + let uuid = Uuid::from_slice(iface_data.if_uuid.as_slice()); + let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + assert_eq!(expected_uuid, uuid); + + let arg = "12345678-1234-1234-1234-123456789ABC,>1"; + let iface_data = parse_iface_data(arg).unwrap(); + let expected_uuid = Ok(String::from("12345678-1234-1234-1234-123456789ABC").to_lowercase()); + let uuid = Uuid::from_slice(iface_data.if_uuid.as_slice()); + let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + assert_eq!(expected_uuid, uuid); + let du16 = iface_data.du16.unwrap(); + assert_eq!(DetectUintMode::DetectUintModeGt, du16.mode); + assert_eq!(1, du16.arg1); + + let arg = "12345678-1234-1234-1234-123456789ABC,any_frag"; + let iface_data = parse_iface_data(arg).unwrap(); + let expected_uuid = Ok(String::from("12345678-1234-1234-1234-123456789ABC").to_lowercase()); + let uuid = Uuid::from_slice(iface_data.if_uuid.as_slice()); + let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + assert_eq!(expected_uuid, uuid); + assert!(iface_data.du16.is_none()); + assert_eq!(1, iface_data.any_frag); + + let arg = "12345678-1234-1234-1234-123456789ABC,!10,any_frag"; + let iface_data = parse_iface_data(arg).unwrap(); + let expected_uuid = Ok(String::from("12345678-1234-1234-1234-123456789ABC").to_lowercase()); + let uuid = Uuid::from_slice(iface_data.if_uuid.as_slice()); + let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + assert_eq!(expected_uuid, uuid); + assert_eq!(1, iface_data.any_frag); + let du16 = iface_data.du16.unwrap(); + assert_eq!(DetectUintMode::DetectUintModeNe, du16.mode); + assert_eq!(10, du16.arg1); + + let arg = "12345678-1234-1234-1234-123456789ABC,>1,ay_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-1234-12345679ABC,>1,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-134-123456789ABC,>1,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-123-124-1234-123456789ABC,>1,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "1234568-1234-1234-1234-123456789ABC,>1,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-1234-123456789ABC,>65536,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-1234-123456789ABC,>=0,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-1234-123456789ABC,<0,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + + let arg = "12345678-1234-1234-1234-123456789ABC,>65535,any_frag"; + let iface_data = parse_iface_data(arg); + assert!(iface_data.is_err()); + } + + #[test] + fn test_parse_opnum_data() { + let arg = "12"; + let opnum_data = parse_opnum_data(arg).unwrap(); + assert_eq!(1, opnum_data.data.len()); + assert_eq!(12, opnum_data.data[0].range1); + assert_eq!( + DETECT_DCE_OPNUM_RANGE_UNINITIALIZED, + opnum_data.data[0].range2 + ); + + let arg = "12,24"; + let opnum_data = parse_opnum_data(arg).unwrap(); + assert_eq!(2, opnum_data.data.len()); + assert_eq!(12, opnum_data.data[0].range1); + assert_eq!(24, opnum_data.data[1].range1); + + let arg = "12,12-24"; + let opnum_data = parse_opnum_data(arg).unwrap(); + assert_eq!(2, opnum_data.data.len()); + assert_eq!(12, opnum_data.data[0].range1); + assert_eq!(12, opnum_data.data[1].range1); + assert_eq!(24, opnum_data.data[1].range2); + + let arg = "12-14,12,121,62-78"; + let opnum_data = parse_opnum_data(arg).unwrap(); + assert_eq!(4, opnum_data.data.len()); + assert_eq!(12, opnum_data.data[0].range1); + assert_eq!(14, opnum_data.data[0].range2); + assert_eq!(121, opnum_data.data[2].range1); + assert_eq!(78, opnum_data.data[3].range2); + + let arg = "12,26,62,61,6513-6666"; + let opnum_data = parse_opnum_data(arg).unwrap(); + assert_eq!(5, opnum_data.data.len()); + assert_eq!(61, opnum_data.data[3].range1); + assert_eq!(6513, opnum_data.data[4].range1); + + let arg = "12,26,62,61,6513--"; + let opnum_data = parse_opnum_data(arg); + assert!(opnum_data.is_err()); + + let arg = "12-14,12,121,62-8"; + let opnum_data = parse_opnum_data(arg); + assert!(opnum_data.is_err()); + } +} diff --git a/rust/src/dcerpc/log.rs b/rust/src/dcerpc/log.rs new file mode 100644 index 0000000..3e73227 --- /dev/null +++ b/rust/src/dcerpc/log.rs @@ -0,0 +1,139 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +use uuid::Uuid; + +use crate::dcerpc::dcerpc::*; +use crate::dcerpc::dcerpc_udp::*; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +fn log_dcerpc_header_tcp( + jsb: &mut JsonBuilder, state: &DCERPCState, tx: &DCERPCTransaction, +) -> Result<(), JsonError> { + if tx.req_done && !tx.req_lost { + jsb.set_string("request", &dcerpc_type_string(tx.req_cmd))?; + match tx.req_cmd { + DCERPC_TYPE_REQUEST => { + jsb.open_object("req")?; + jsb.set_uint("opnum", tx.opnum as u64)?; + jsb.set_uint("frag_cnt", tx.frag_cnt_ts as u64)?; + jsb.set_uint("stub_data_size", tx.stub_data_buffer_ts.len() as u64)?; + jsb.close()?; + } + DCERPC_TYPE_BIND => match &state.bind { + Some(bind) => { + jsb.open_array("interfaces")?; + for uuid in &bind.uuid_list { + jsb.start_object()?; + let ifstr = Uuid::from_slice(uuid.uuid.as_slice()); + let ifstr = ifstr.map(|uuid| uuid.to_hyphenated().to_string()).unwrap(); + jsb.set_string("uuid", &ifstr)?; + let vstr = format!("{}.{}", uuid.version, uuid.versionminor); + jsb.set_string("version", &vstr)?; + jsb.set_uint("ack_result", uuid.result as u64)?; + jsb.close()?; + } + jsb.close()?; + } + None => {} + }, + _ => {} + } + } else { + jsb.set_string("request", "REQUEST_LOST")?; + } + + if tx.resp_done && !tx.resp_lost { + jsb.set_string("response", &dcerpc_type_string(tx.resp_cmd))?; + #[allow(clippy::single_match)] + match tx.resp_cmd { + DCERPC_TYPE_RESPONSE => { + jsb.open_object("res")?; + jsb.set_uint("frag_cnt", tx.frag_cnt_tc as u64)?; + jsb.set_uint("stub_data_size", tx.stub_data_buffer_tc.len() as u64)?; + jsb.close()?; + } + _ => {} // replicating behavior from smb + } + } else { + jsb.set_string("response", "UNREPLIED")?; + } + + if let Some(ref hdr) = state.header { + jsb.set_uint("call_id", tx.call_id as u64)?; + let vstr = format!("{}.{}", hdr.rpc_vers, hdr.rpc_vers_minor); + jsb.set_string("rpc_version", &vstr)?; + } + + return Ok(()); +} + +fn log_dcerpc_header_udp( + jsb: &mut JsonBuilder, _state: &DCERPCUDPState, tx: &DCERPCTransaction, +) -> Result<(), JsonError> { + if tx.req_done && !tx.req_lost { + jsb.set_string("request", &dcerpc_type_string(tx.req_cmd))?; + #[allow(clippy::single_match)] + match tx.req_cmd { + DCERPC_TYPE_REQUEST => { + jsb.open_object("req")?; + jsb.set_uint("opnum", tx.opnum as u64)?; + jsb.set_uint("frag_cnt", tx.frag_cnt_ts as u64)?; + jsb.set_uint("stub_data_size", tx.stub_data_buffer_ts.len() as u64)?; + jsb.close()?; + } + _ => {} + } + } else { + jsb.set_string("request", "REQUEST_LOST")?; + } + + if tx.resp_done && !tx.resp_lost { + jsb.set_string("response", &dcerpc_type_string(tx.resp_cmd))?; + #[allow(clippy::single_match)] + match tx.resp_cmd { + DCERPC_TYPE_RESPONSE => { + jsb.open_object("res")?; + jsb.set_uint("frag_cnt", tx.frag_cnt_tc as u64)?; + jsb.set_uint("stub_data_size", tx.stub_data_buffer_tc.len() as u64)?; + jsb.close()?; + } + _ => {} // replicating behavior from smb + } + } else { + jsb.set_string("response", "UNREPLIED")?; + } + let activityuuid = Uuid::from_slice(tx.activityuuid.as_slice()); + let activityuuid = activityuuid.map(|uuid| uuid.to_hyphenated().to_string()).unwrap(); + jsb.set_string("activityuuid", &activityuuid)?; + jsb.set_uint("seqnum", tx.seqnum as u64)?; + jsb.set_string("rpc_version", "4.0")?; + return Ok(()); +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_log_json_record_tcp( + state: &DCERPCState, tx: &DCERPCTransaction, jsb: &mut JsonBuilder, +) -> bool { + log_dcerpc_header_tcp(jsb, state, tx).is_ok() +} + +#[no_mangle] +pub extern "C" fn rs_dcerpc_log_json_record_udp( + state: &DCERPCUDPState, tx: &DCERPCTransaction, jsb: &mut JsonBuilder, +) -> bool { + log_dcerpc_header_udp(jsb, state, tx).is_ok() +} diff --git a/rust/src/dcerpc/mod.rs b/rust/src/dcerpc/mod.rs new file mode 100644 index 0000000..800d2ad --- /dev/null +++ b/rust/src/dcerpc/mod.rs @@ -0,0 +1,24 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! DCE/RPC protocol parser, logger and detection module. + +pub mod dcerpc; +pub mod dcerpc_udp; +pub mod parser; +pub mod detect; +pub mod log; diff --git a/rust/src/dcerpc/parser.rs b/rust/src/dcerpc/parser.rs new file mode 100644 index 0000000..2562598 --- /dev/null +++ b/rust/src/dcerpc/parser.rs @@ -0,0 +1,346 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +use crate::dcerpc::dcerpc::{ + BindCtxItem, DCERPCBind, DCERPCBindAck, DCERPCBindAckResult, DCERPCHdr, DCERPCRequest, Uuid, +}; +use crate::dcerpc::dcerpc_udp::DCERPCHdrUdp; +use nom7::bytes::streaming::take; +use nom7::combinator::cond; +use nom7::number::complete::{le_u16, le_u32, le_u8, u16, u32}; +use nom7::number::Endianness; +use nom7::multi::count; +use nom7::IResult; + +fn uuid_to_vec(uuid: Uuid) -> Vec<u8> { + let mut uuidtmp = uuid; + let mut vect: Vec<u8> = Vec::new(); + vect.append(&mut uuidtmp.time_low); + vect.append(&mut uuidtmp.time_mid); + vect.append(&mut uuidtmp.time_hi_and_version); + vect.push(uuidtmp.clock_seq_hi_and_reserved); + vect.push(uuidtmp.clock_seq_low); + vect.append(&mut uuidtmp.node); + vect +} + +fn assemble_uuid(uuid: Uuid) -> Vec<u8> { + let mut uuidtmp = uuid; + let mut vect: Vec<u8> = Vec::new(); + uuidtmp.time_low.reverse(); + uuidtmp.time_mid.reverse(); + uuidtmp.time_hi_and_version.reverse(); + vect.append(&mut uuidtmp.time_low); + vect.append(&mut uuidtmp.time_mid); + vect.append(&mut uuidtmp.time_hi_and_version); + vect.push(uuidtmp.clock_seq_hi_and_reserved); + vect.push(uuidtmp.clock_seq_low); + vect.append(&mut uuidtmp.node); + + vect +} + +pub fn parse_uuid(i: &[u8]) -> IResult<&[u8], Uuid> { + let (i, time_low) = take(4_usize)(i)?; + let (i, time_mid) = take(2_usize)(i)?; + let (i, time_hi_and_version) = take(2_usize)(i)?; + let (i, clock_seq_hi_and_reserved) = le_u8(i)?; + let (i, clock_seq_low) = le_u8(i)?; + let (i, node) = take(6_usize)(i)?; + let uuid = Uuid { + time_low: time_low.to_vec(), + time_mid: time_mid.to_vec(), + time_hi_and_version: time_hi_and_version.to_vec(), + clock_seq_hi_and_reserved, + clock_seq_low, + node: node.to_vec(), + }; + Ok((i, uuid)) +} + +pub fn parse_dcerpc_udp_header(i: &[u8]) -> IResult<&[u8], DCERPCHdrUdp> { + let (i, rpc_vers) = le_u8(i)?; + let (i, pkt_type) = le_u8(i)?; + let (i, flags1) = le_u8(i)?; + let (i, flags2) = le_u8(i)?; + let (i, drep) = take(3_usize)(i)?; + let endianness = if drep[0] == 0 { Endianness::Big } else { Endianness::Little }; + let (i, serial_hi) = le_u8(i)?; + let (i, objectuuid) = take(16_usize)(i)?; + let (i, interfaceuuid) = take(16_usize)(i)?; + let (i, activityuuid) = take(16_usize)(i)?; + let (i, server_boot) = u32(endianness)(i)?; + let (i, if_vers) = u32(endianness)(i)?; + let (i, seqnum) = u32(endianness)(i)?; + let (i, opnum) = u16(endianness)(i)?; + let (i, ihint) = u16(endianness)(i)?; + let (i, ahint) = u16(endianness)(i)?; + let (i, fraglen) = u16(endianness)(i)?; + let (i, fragnum) = u16(endianness)(i)?; + let (i, auth_proto) = le_u8(i)?; + let (i, serial_lo) = le_u8(i)?; + let header = DCERPCHdrUdp { + rpc_vers, + pkt_type, + flags1, + flags2, + drep: drep.to_vec(), + serial_hi, + objectuuid: match parse_uuid(objectuuid) { + Ok((_, vect)) => assemble_uuid(vect), + Err(_e) => { + SCLogDebug!("{}", _e); + vec![0] + }, + }, + interfaceuuid: match parse_uuid(interfaceuuid) { + Ok((_, vect)) => assemble_uuid(vect), + Err(_e) => { + SCLogDebug!("{}", _e); + vec![0] + }, + }, + activityuuid: match parse_uuid(activityuuid){ + Ok((_, vect)) => assemble_uuid(vect), + Err(_e) => { + SCLogDebug!("{}", _e); + vec![0] + }, + }, + server_boot, + if_vers, + seqnum, + opnum, + ihint, + ahint, + fraglen, + fragnum, + auth_proto, + serial_lo, + }; + Ok((i, header)) +} + +pub fn parse_dcerpc_bindack_result(i: &[u8]) -> IResult<&[u8], DCERPCBindAckResult> { + let (i, ack_result) = le_u16(i)?; + let (i, ack_reason) = le_u16(i)?; + let (i, transfer_syntax) = take(16_usize)(i)?; + let (i, syntax_version) = le_u32(i)?; + let result = DCERPCBindAckResult { + ack_result, + ack_reason, + transfer_syntax: transfer_syntax.to_vec(), + syntax_version, + }; + Ok((i, result)) +} + +pub fn parse_dcerpc_bindack(i: &[u8]) -> IResult<&[u8], DCERPCBindAck> { + let (i, _max_xmit_frag) = le_u16(i)?; + let (i, _max_recv_frag) = le_u16(i)?; + let (i, _assoc_group) = take(4_usize)(i)?; + let (i, sec_addr_len) = le_u16(i)?; + let (i, _) = take(sec_addr_len)(i)?; + let (i, _) = cond((sec_addr_len.wrapping_add(2)) % 4 != 0, |b| take(4 - (sec_addr_len.wrapping_add(2)) % 4)(b))(i)?; + let (i, numctxitems) = le_u8(i)?; + let (i, _) = take(3_usize)(i)?; // Padding + let (i, ctxitems) = count(parse_dcerpc_bindack_result, numctxitems as usize)(i)?; + let result = DCERPCBindAck { + accepted_uuid_list: Vec::new(), + sec_addr_len, + numctxitems, + ctxitems, + }; + Ok((i, result)) +} + +pub fn parse_bindctx_item(i: &[u8], endianness: Endianness) -> IResult<&[u8], BindCtxItem> { + let (i, ctxid) = u16(endianness)(i)?; + let (i, _num_trans_items) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // Reserved bit + let (i, uuid) = take(16_usize)(i)?; + let (i, version) = u16(endianness)(i)?; + let (i, versionminor) = u16(endianness)(i)?; + let (i, _) = take(20_usize)(i)?; + let result = BindCtxItem { + ctxid, + // UUID parsing for TCP seems to change as per endianness + uuid: match parse_uuid(uuid) { + Ok((_, vect)) => match endianness { + Endianness::Little => assemble_uuid(vect), + _ => uuid_to_vec(vect), + }, + // Shouldn't happen + Err(_e) => {vec![0]}, + }, + version, + versionminor, + }; + Ok((i, result)) +} + +pub fn parse_dcerpc_bind(i: &[u8]) -> IResult<&[u8], DCERPCBind> { + let (i, _max_xmit_frag) = le_u16(i)?; + let (i, _max_recv_frag) = le_u16(i)?; + let (i, _assoc_group_id) = le_u32(i)?; + let (i, numctxitems) = le_u8(i)?; + let (i, _) = take(3_usize)(i)?; + let result = DCERPCBind { + numctxitems, + uuid_list: Vec::new(), + }; + Ok((i, result)) +} + +pub fn parse_dcerpc_header(i: &[u8]) -> IResult<&[u8], DCERPCHdr> { + let (i, rpc_vers) = le_u8(i)?; + let (i, rpc_vers_minor) = le_u8(i)?; + let (i, hdrtype) = le_u8(i)?; + let (i, pfc_flags) = le_u8(i)?; + let (i, packed_drep) = take(4_usize)(i)?; + let endianness = if packed_drep[0] & 0x10 == 0 { Endianness::Big } else { Endianness::Little }; + let (i, frag_length) = u16(endianness)(i)?; + let (i, auth_length) = u16(endianness)(i)?; + let (i, call_id) = u32(endianness)(i)?; + let header = DCERPCHdr { + rpc_vers, + rpc_vers_minor, + hdrtype, + pfc_flags, + packed_drep: packed_drep.to_vec(), + frag_length, + auth_length, + call_id, + }; + Ok((i, header)) +} + +pub fn parse_dcerpc_request(i: &[u8], endianness: Endianness) -> IResult<&[u8], DCERPCRequest> { + let (i, _pad) = take(4_usize)(i)?; + let (i, ctxid) = u16(endianness)(i)?; + let (i, opnum) = u16(endianness)(i)?; + let req = DCERPCRequest { + ctxid, + opnum, + first_request_seen: 1, + }; + Ok((i, req)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_uuid() { + let uuid: &[u8] = &[ + 0xb8, 0x4a, 0x9f, 0x4d, 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, 0x00, 0x20, 0xaf, 0x6e, + 0x7c, 0x57, + ]; + let expected_uuid = Uuid { + time_low: vec![0xb8, 0x4a, 0x9f, 0x4d], + time_mid: vec![0x1c, 0x7d], + time_hi_and_version: vec![0xcf, 0x11], + clock_seq_hi_and_reserved: 0x86, + clock_seq_low: 0x1e, + node: vec![0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57], + }; + let (_remainder, parsed_uuid) = parse_uuid(uuid).unwrap(); + assert_eq!(expected_uuid, parsed_uuid); + } + + #[test] + fn test_assemble_uuid() { + let uuid = Uuid { + time_low: vec![0xb8, 0x4a, 0x9f, 0x4d], + time_mid: vec![0x1c, 0x7d], + time_hi_and_version: vec![0xcf, 0x11], + clock_seq_hi_and_reserved: 0x86, + clock_seq_low: 0x1e, + node: vec![0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57], + }; + let expected_val = vec![ + 0x4d, 0x9f, 0x4a, 0xb8, 0x7d, 0x1c, 0x11, 0xcf, 0x86, 0x1e, 0x00, 0x20, 0xaf, 0x6e, + 0x7c, 0x57, + ]; + assert_eq!(expected_val, assemble_uuid(uuid)); + } + + #[test] + fn test_parse_dcerpc_udp_header() { + let dcerpcheader: &[u8] = &[ + 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0x4a, 0x9f, 0x4d, + 0x1c, 0x7d, 0xcf, 0x11, 0x86, 0x1e, 0x00, 0x20, 0xaf, 0x6e, 0x7c, 0x57, 0x86, 0xc2, + 0x37, 0x67, 0xf7, 0x1e, 0xd1, 0x11, 0xbc, 0xd9, 0x00, 0x60, 0x97, 0x92, 0xd2, 0x6c, + 0x79, 0xbe, 0x01, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x68, 0x00, 0x00, 0x00, 0x0a, 0x00, + ]; + let (_remainder, header) = parse_dcerpc_udp_header(dcerpcheader).unwrap(); + let expected_activityuuid = vec![ + 0x67, 0x37, 0xc2, 0x86, 0x1e, 0xf7, 0x11, 0xd1, 0xbc, 0xd9, 0x00, 0x60, 0x97, 0x92, + 0xd2, 0x6c, + ]; + assert_eq!(0x04, header.rpc_vers); + assert_eq!(0x00, header.pkt_type); + assert_eq!(0x08, header.flags1); + assert_eq!(0x00, header.flags2); + assert_eq!(vec!(0x10, 0x00, 0x00), header.drep); + assert_eq!(0x00, header.serial_hi); + assert_eq!(expected_activityuuid, header.activityuuid); + assert_eq!(0x3401be79, header.server_boot); + assert_eq!(0x00000000, header.seqnum); + assert_eq!(0xffff, header.ihint); + assert_eq!(0x0068, header.fraglen); + assert_eq!(0x0a, header.auth_proto); + } + + #[test] + fn test_parse_dcerpc_header() { + let dcerpcheader: &[u8] = &[ + 0x05, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + let (_remainder, header) = parse_dcerpc_header(dcerpcheader).unwrap(); + assert_eq!(5, header.rpc_vers); + assert_eq!(0, header.rpc_vers_minor); + assert_eq!(0, header.hdrtype); + assert_eq!(1024, header.frag_length); + } + + #[test] + fn test_parse_dcerpc_bind() { + let dcerpcbind: &[u8] = &[ + 0xd0, 0x16, 0xd0, 0x16, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + ]; + let (_remainder, bind) = parse_dcerpc_bind(dcerpcbind).unwrap(); + assert_eq!(24, bind.numctxitems); + } + + #[test] + fn test_parse_bindctx_item() { + let dcerpcbind: &[u8] = &[ + 0x00, 0x00, 0x01, 0x00, 0x2c, 0xd0, 0x28, 0xda, 0x76, 0x91, 0xf6, 0x6e, 0xcb, 0x0f, + 0xbf, 0x85, 0xcd, 0x9b, 0xf6, 0x39, 0x01, 0x00, 0x03, 0x00, 0x04, 0x5d, 0x88, 0x8a, + 0xeb, 0x1c, 0xc9, 0x11, 0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00, + 0x00, 0x00, + ]; + let (_remainder, ctxitem) = parse_bindctx_item(dcerpcbind, Endianness::Little).unwrap(); + assert_eq!(0, ctxitem.ctxid); + assert_eq!(1, ctxitem.version); + assert_eq!(3, ctxitem.versionminor); + } +} diff --git a/rust/src/detect/byte_math.rs b/rust/src/detect/byte_math.rs new file mode 100644 index 0000000..80bd3d5 --- /dev/null +++ b/rust/src/detect/byte_math.rs @@ -0,0 +1,1163 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Jeff Lucovsky <jlucovsky@oisf.net> + +use crate::detect::error::RuleParseError; +use crate::detect::parser::{parse_token, take_until_whitespace}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +use nom7::bytes::complete::tag; +use nom7::character::complete::multispace0; +use nom7::sequence::preceded; +use nom7::{Err, IResult}; +use std::str; + +pub const DETECT_BYTEMATH_FLAG_RELATIVE: u8 = 0x01; +pub const DETECT_BYTEMATH_FLAG_STRING: u8 = 0x02; +pub const DETECT_BYTEMATH_FLAG_BITMASK: u8 = 0x04; +pub const DETECT_BYTEMATH_FLAG_ENDIAN: u8 = 0x08; +pub const DETECT_BYTEMATH_FLAG_RVALUE_VAR: u8 = 0x10; +pub const DETECT_BYTEMATH_FLAG_NBYTES_VAR: u8 = 0x20; + +// Ensure required values are provided +const DETECT_BYTEMATH_FLAG_NBYTES: u8 = 0x1; +const DETECT_BYTEMATH_FLAG_OFFSET: u8 = 0x2; +const DETECT_BYTEMATH_FLAG_OPER: u8 = 0x4; +const DETECT_BYTEMATH_FLAG_RVALUE: u8 = 0x8; +const DETECT_BYTEMATH_FLAG_RESULT: u8 = 0x10; +const DETECT_BYTEMATH_FLAG_REQUIRED: u8 = DETECT_BYTEMATH_FLAG_RESULT + | DETECT_BYTEMATH_FLAG_RVALUE + | DETECT_BYTEMATH_FLAG_NBYTES + | DETECT_BYTEMATH_FLAG_OFFSET + | DETECT_BYTEMATH_FLAG_OPER; + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +// operators: +, -, /, *, <<, >> +pub enum ByteMathOperator { + OperatorNone = 1, + Addition = 2, + Subtraction = 3, + Division = 4, + Multiplication = 5, + LeftShift = 6, + RightShift = 7, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +// endian <big|little|dce> +pub enum ByteMathEndian { + _EndianNone = 0, + BigEndian = 1, + LittleEndian = 2, + EndianDCE = 3, +} +pub const DETECT_BYTEMATH_ENDIAN_DEFAULT: ByteMathEndian = ByteMathEndian::BigEndian; + +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ByteMathBase { + _BaseNone = 0, + BaseOct = 8, + BaseDec = 10, + BaseHex = 16, +} +const BASE_DEFAULT: ByteMathBase = ByteMathBase::BaseDec; + +// Fixed position parameter count: bytes, offset, oper, rvalue, result +// result is not parsed with the fixed position parameters as it's +// often swapped with optional parameters +pub const DETECT_BYTEMATH_FIXED_PARAM_COUNT: usize = 5; +// Optional parameters: endian, relative, string, dce, bitmask +pub const DETECT_BYTEMATH_MAX_PARAM_COUNT: usize = 10; + +#[derive(Debug)] +enum ResultValue { + Numeric(u64), + String(String), +} + +#[repr(C)] +#[derive(Debug)] +pub struct DetectByteMathData { + rvalue_str: *const c_char, + result: *const c_char, + nbytes_str: *const c_char, + rvalue: u32, + offset: i32, + bitmask_val: u32, + bitmask_shift_count: u16, + id: u16, + flags: u8, + local_id: u8, + nbytes: u8, + oper: ByteMathOperator, + endian: ByteMathEndian, // big, little, dce + base: ByteMathBase, // From string or dce +} + +impl Drop for DetectByteMathData { + fn drop(&mut self) { + unsafe { + if !self.result.is_null() { + let _ = CString::from_raw(self.result as *mut c_char); + } + if !self.rvalue_str.is_null() { + let _ = CString::from_raw(self.rvalue_str as *mut c_char); + } + if !self.nbytes_str.is_null() { + let _ = CString::from_raw(self.nbytes_str as *mut c_char); + } + } + } +} + +impl Default for DetectByteMathData { + fn default() -> Self { + DetectByteMathData { + local_id: 0, + flags: 0, + nbytes: 0, + offset: 0, + oper: ByteMathOperator::OperatorNone, + rvalue_str: std::ptr::null_mut(), + nbytes_str: std::ptr::null_mut(), + rvalue: 0, + result: std::ptr::null_mut(), + endian: DETECT_BYTEMATH_ENDIAN_DEFAULT, + base: BASE_DEFAULT, + bitmask_val: 0, + bitmask_shift_count: 0, + id: 0, + } + } +} + +impl DetectByteMathData { + pub fn new() -> Self { + Self { + ..Default::default() + } + } +} + +fn get_string_value(value: &str) -> Result<ByteMathBase, ()> { + let res = match value { + "hex" => ByteMathBase::BaseHex, + "oct" => ByteMathBase::BaseOct, + "dec" => ByteMathBase::BaseDec, + _ => return Err(()), + }; + + Ok(res) +} + +fn get_oper_value(value: &str) -> Result<ByteMathOperator, ()> { + let res = match value { + "+" => ByteMathOperator::Addition, + "-" => ByteMathOperator::Subtraction, + "/" => ByteMathOperator::Division, + "*" => ByteMathOperator::Multiplication, + "<<" => ByteMathOperator::LeftShift, + ">>" => ByteMathOperator::RightShift, + _ => return Err(()), + }; + + Ok(res) +} + +fn get_endian_value(value: &str) -> Result<ByteMathEndian, ()> { + let res = match value { + "big" => ByteMathEndian::BigEndian, + "little" => ByteMathEndian::LittleEndian, + "dce" => ByteMathEndian::EndianDCE, + _ => return Err(()), + }; + + Ok(res) +} + +// Parsed as a u64 for validation with u32 {min,max} so values greater than uint32 +// are not treated as a string value. +fn parse_var(input: &str) -> IResult<&str, ResultValue, RuleParseError<&str>> { + let (input, value) = parse_token(input)?; + if let Ok(val) = value.parse::<u64>() { + Ok((input, ResultValue::Numeric(val))) + } else { + Ok((input, ResultValue::String(value.to_string()))) + } +} + +fn parse_bytemath(input: &str) -> IResult<&str, DetectByteMathData, RuleParseError<&str>> { + // Inner utility function for easy error creation. + fn make_error(reason: String) -> nom7::Err<RuleParseError<&'static str>> { + Err::Error(RuleParseError::InvalidByteMath(reason)) + } + let (_, values) = nom7::multi::separated_list1( + tag(","), + preceded(multispace0, nom7::bytes::complete::is_not(",")), + )(input)?; + + if values.len() < DETECT_BYTEMATH_FIXED_PARAM_COUNT + || values.len() > DETECT_BYTEMATH_MAX_PARAM_COUNT + { + return Err(make_error(format!("Incorrect argument string; at least {} values must be specified but no more than {}: {:?}", + DETECT_BYTEMATH_FIXED_PARAM_COUNT, DETECT_BYTEMATH_MAX_PARAM_COUNT, input))); + } + + let mut required_flags: u8 = 0; + let mut byte_math = DetectByteMathData::new(); + //for value in &values[0..] { + for value in values { + let (mut val, mut name) = take_until_whitespace(value)?; + val = val.trim(); + name = name.trim(); + match name { + "oper" => { + if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OPER) { + return Err(make_error("operator already set".to_string())); + } + byte_math.oper = match get_oper_value(val) { + Ok(val) => val, + Err(_) => { + return Err(make_error(format!("unknown oper value {}", val))); + } + }; + required_flags |= DETECT_BYTEMATH_FLAG_OPER; + } + "result" => { + if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RESULT) { + return Err(make_error("result already set".to_string())); + } + let tmp: String = val + .parse() + .map_err(|_| make_error(format!("invalid result: {}", val)))?; + match CString::new(tmp) { + Ok(strval) => { + byte_math.result = strval.into_raw(); + required_flags |= DETECT_BYTEMATH_FLAG_RESULT; + } + _ => { + return Err(make_error( + "parse string not safely convertible to C".to_string(), + )); + } + } + } + "rvalue" => { + if 0 != (required_flags & DETECT_BYTEMATH_FLAG_RVALUE) { + return Err(make_error("rvalue already set".to_string())); + } + let (_, res) = parse_var(val)?; + match res { + ResultValue::Numeric(val) => { + if val >= u32::MIN.into() && val <= u32::MAX.into() { + byte_math.rvalue = val as u32 + } else { + return Err(make_error(format!( + "invalid rvalue value: must be between {} and {}: {}", + 1, + u32::MAX, + val + ))); + } + } + ResultValue::String(val) => match CString::new(val) { + Ok(newval) => { + byte_math.rvalue_str = newval.into_raw(); + byte_math.flags |= DETECT_BYTEMATH_FLAG_RVALUE_VAR; + } + _ => { + return Err(make_error( + "parse string not safely convertible to C".to_string(), + )) + } + }, + } + required_flags |= DETECT_BYTEMATH_FLAG_RVALUE; + } + "endian" => { + if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) { + return Err(make_error("endianess already set".to_string())); + } + byte_math.endian = match get_endian_value(val) { + Ok(val) => val, + Err(_) => { + return Err(make_error(format!("invalid endian value: {}", val))); + } + }; + byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN; + } + "dce" => { + if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_ENDIAN) { + return Err(make_error("endianess already set".to_string())); + } + byte_math.flags |= DETECT_BYTEMATH_FLAG_ENDIAN; + byte_math.endian = ByteMathEndian::EndianDCE; + } + "string" => { + if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_STRING) { + return Err(make_error("string already set".to_string())); + } + byte_math.base = match get_string_value(val) { + Ok(val) => val, + Err(_) => { + return Err(make_error(format!("invalid string value: {}", val))); + } + }; + byte_math.flags |= DETECT_BYTEMATH_FLAG_STRING; + } + "relative" => { + if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_RELATIVE) { + return Err(make_error("relative already set".to_string())); + } + byte_math.flags |= DETECT_BYTEMATH_FLAG_RELATIVE; + } + "bitmask" => { + if 0 != (byte_math.flags & DETECT_BYTEMATH_FLAG_BITMASK) { + return Err(make_error("bitmask already set".to_string())); + } + let trimmed = if val.starts_with("0x") || val.starts_with("0X") { + &val[2..] + } else { + val + }; + + let val = u32::from_str_radix(trimmed, 16) + .map_err(|_| make_error(format!("invalid bitmask value: {}", value)))?; + byte_math.bitmask_val = val; + byte_math.flags |= DETECT_BYTEMATH_FLAG_BITMASK; + } + "offset" => { + if 0 != (required_flags & DETECT_BYTEMATH_FLAG_OFFSET) { + return Err(make_error("offset already set".to_string())); + } + byte_math.offset = val + .parse::<i32>() + .map_err(|_| make_error(format!("invalid offset value: {}", val)))?; + if byte_math.offset > 65535 || byte_math.offset < -65535 { + return Err(make_error(format!( + "invalid offset value: must be between -65535 and 65535: {}", + val + ))); + } + required_flags |= DETECT_BYTEMATH_FLAG_OFFSET; + } + "bytes" => { + if 0 != (required_flags & DETECT_BYTEMATH_FLAG_NBYTES) { + return Err(make_error("nbytes already set".to_string())); + } + let (_, res) = parse_var(val)?; + match res { + ResultValue::Numeric(val) => { + if (1..=10).contains(&val) { + byte_math.nbytes = val as u8 + } else { + return Err(make_error(format!( + "invalid nbytes value: must be between 1 and 10: {}", + val + ))); + } + } + ResultValue::String(val) => match CString::new(val) { + Ok(newval) => { + byte_math.nbytes_str = newval.into_raw(); + byte_math.flags |= DETECT_BYTEMATH_FLAG_NBYTES_VAR; + } + _ => { + return Err(make_error( + "parse string not safely convertible to C".to_string(), + )) + } + }, + } + required_flags |= DETECT_BYTEMATH_FLAG_NBYTES; + } + _ => { + return Err(make_error(format!("unknown byte_math keyword: {}", name))); + } + }; + } + + // Ensure required values are present + if (required_flags & DETECT_BYTEMATH_FLAG_REQUIRED) != DETECT_BYTEMATH_FLAG_REQUIRED { + return Err(make_error(format!( + "required byte_math parameters missing: \"{:?}\"", + input + ))); + } + + // Using left/right shift further restricts the value of nbytes. Note that + // validation has already ensured nbytes is in [1..10] + match byte_math.oper { + ByteMathOperator::LeftShift | ByteMathOperator::RightShift => { + if byte_math.nbytes > 4 { + return Err(make_error(format!("nbytes must be 1 through 4 (inclusive) when used with \"<<\" or \">>\"; {} is not valid", byte_math.nbytes))); + } + } + _ => {} + }; + + Ok((input, byte_math)) +} + +/// Intermediary function between the C code and the parsing functions. +#[no_mangle] +pub unsafe extern "C" fn ScByteMathParse(c_arg: *const c_char) -> *mut DetectByteMathData { + if c_arg.is_null() { + return std::ptr::null_mut(); + } + + let arg = match CStr::from_ptr(c_arg).to_str() { + Ok(arg) => arg, + Err(_) => { + return std::ptr::null_mut(); + } + }; + match parse_bytemath(arg) { + Ok((_, detect)) => return Box::into_raw(Box::new(detect)), + Err(_) => return std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn ScByteMathFree(ptr: *mut DetectByteMathData) { + if !ptr.is_null() { + let _ = Box::from_raw(ptr); + } +} + +#[cfg(test)] +mod tests { + use super::*; + // structure equality only used by test cases + impl PartialEq for DetectByteMathData { + fn eq(&self, other: &Self) -> bool { + let mut res: bool = false; + + if !self.rvalue_str.is_null() && !other.rvalue_str.is_null() { + let s_val = unsafe { CStr::from_ptr(self.rvalue_str) }; + let o_val = unsafe { CStr::from_ptr(other.rvalue_str) }; + res = s_val == o_val; + } else if !self.rvalue_str.is_null() || !other.rvalue_str.is_null() { + return false; + } + + if !self.nbytes_str.is_null() && !other.nbytes_str.is_null() { + let s_val = unsafe { CStr::from_ptr(self.nbytes_str) }; + let o_val = unsafe { CStr::from_ptr(other.nbytes_str) }; + res = s_val == o_val; + } else if !self.nbytes_str.is_null() || !other.nbytes_str.is_null() { + return false; + } + + if !self.result.is_null() && !self.result.is_null() { + let s_val = unsafe { CStr::from_ptr(self.result) }; + let o_val = unsafe { CStr::from_ptr(other.result) }; + res = s_val == o_val; + } else if !self.result.is_null() || !self.result.is_null() { + return false; + } + + res && self.local_id == other.local_id + && self.flags == other.flags + && self.nbytes == other.nbytes + && self.offset == other.offset + && self.oper == other.oper + && self.rvalue == other.rvalue + && self.endian == other.endian + && self.base == other.base + && self.bitmask_val == other.bitmask_val + && self.bitmask_shift_count == other.bitmask_shift_count + && self.id == other.id + } + } + + fn valid_test( + args: &str, nbytes: u8, offset: i32, oper: ByteMathOperator, rvalue_str: &str, nbytes_str: &str, rvalue: u32, + result: &str, base: ByteMathBase, endian: ByteMathEndian, bitmask_val: u32, flags: u8, + ) { + let bmd = DetectByteMathData { + nbytes, + offset, + oper, + rvalue_str: if !rvalue_str.is_empty() { + CString::new(rvalue_str).unwrap().into_raw() + } else { + std::ptr::null_mut() + }, + nbytes_str: if !nbytes_str.is_empty() { + CString::new(nbytes_str).unwrap().into_raw() + } else { + std::ptr::null_mut() + }, + rvalue, + result: CString::new(result).unwrap().into_raw(), + base, + endian, + bitmask_val, + flags, + ..Default::default() + }; + + match parse_bytemath(args) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + fn test_parser_valid() { + valid_test( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result myresult, dce, string dec", + 4, + 3933, + ByteMathOperator::Addition, + "myrvalue", + "", + 0, + "myresult", + ByteMathBase::BaseDec, + ByteMathEndian::EndianDCE, + 0, + DETECT_BYTEMATH_FLAG_RVALUE_VAR + | DETECT_BYTEMATH_FLAG_STRING + | DETECT_BYTEMATH_FLAG_ENDIAN, + ); + + valid_test( + "bytes 4, offset 3933, oper +, rvalue 99, result other, dce, string dec", + 4, + 3933, + ByteMathOperator::Addition, + "", + "", + 99, + "other", + ByteMathBase::BaseDec, + ByteMathEndian::EndianDCE, + 0, + DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN, + ); + + valid_test( + "bytes 4, offset -3933, oper +, rvalue myrvalue, result foo", + 4, + -3933, + ByteMathOperator::Addition, + "rvalue", + "", + 0, + "foo", + BASE_DEFAULT, + ByteMathEndian::BigEndian, + 0, + DETECT_BYTEMATH_FLAG_RVALUE_VAR, + ); + + valid_test( + "bytes nbytes_var, offset -3933, oper +, rvalue myrvalue, result foo", + 0, + -3933, + ByteMathOperator::Addition, + "rvalue", + "nbytes_var", + 0, + "foo", + BASE_DEFAULT, + ByteMathEndian::BigEndian, + 0, + DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_NBYTES_VAR, + ); + + // Out of order + valid_test( + "string dec, endian big, result other, rvalue 99, oper +, offset 3933, bytes 4", + 4, + 3933, + ByteMathOperator::Addition, + "", + "", + 99, + "other", + ByteMathBase::BaseDec, + ByteMathEndian::BigEndian, + 0, + DETECT_BYTEMATH_FLAG_STRING | DETECT_BYTEMATH_FLAG_ENDIAN, + ); + } + + #[test] + fn test_parser_string_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: 3933, + oper: ByteMathOperator::Addition, + rvalue_str: CString::new("myrvalue").unwrap().into_raw(), + rvalue: 0, + result: CString::new("foo").unwrap().into_raw(), + endian: DETECT_BYTEMATH_ENDIAN_DEFAULT, + base: ByteMathBase::BaseDec, + flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING, + ..Default::default() + }; + + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string dec", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR; + bmd.base = BASE_DEFAULT; + match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_STRING; + bmd.base = ByteMathBase::BaseHex; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hex", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.base = ByteMathBase::BaseOct; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string oct", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + fn test_parser_string_invalid() { + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string decimal" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string hexadecimal" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, string octal" + ) + .is_err() + ); + } + + #[test] + // bytes must be between 1 and 10; when combined with rshift/lshift, must be 4 or less + fn test_parser_bytes_invalid() { + assert!( + parse_bytemath("bytes 0, offset 3933, oper +, rvalue myrvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 11, offset 3933, oper +, rvalue myrvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 5, offset 3933, oper >>, rvalue myrvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 5, offset 3933, oper <<, rvalue myrvalue, result foo").is_err() + ); + } + + #[test] + fn test_parser_bitmask_invalid() { + assert!( + parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x") + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask x12345678" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask X12345678" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x123456789012" + ) + .is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0q") + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask maple" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0xGHIJKLMN" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask #*#*@-" + ) + .is_err() + ); + } + + #[test] + fn test_parser_bitmask_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: 3933, + oper: ByteMathOperator::Addition, + rvalue_str: CString::new("myrvalue").unwrap().into_raw(), + rvalue: 0, + result: CString::new("foo").unwrap().into_raw(), + endian: ByteMathEndian::BigEndian, + base: BASE_DEFAULT, + flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_BITMASK, + ..Default::default() + }; + + bmd.bitmask_val = 0x12345678; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0x12345678", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.bitmask_val = 0xffff1234; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask ffff1234", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.bitmask_val = 0xffff1234; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, bitmask 0Xffff1234", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + #[test] + fn test_parser_endian_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: 3933, + oper: ByteMathOperator::Addition, + rvalue_str: CString::new("myrvalue").unwrap().into_raw(), + rvalue: 0, + result: CString::new("foo").unwrap().into_raw(), + endian: ByteMathEndian::BigEndian, + base: BASE_DEFAULT, + flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR | DETECT_BYTEMATH_FLAG_ENDIAN, + ..Default::default() + }; + + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.endian = ByteMathEndian::LittleEndian; + match parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian little", + ) { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.endian = ByteMathEndian::EndianDCE; + match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, dce") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.endian = DETECT_BYTEMATH_ENDIAN_DEFAULT; + bmd.flags = DETECT_BYTEMATH_FLAG_RVALUE_VAR; + match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + fn test_parser_endian_invalid() { + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian bigger" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian smaller" + ) + .is_err() + ); + + // endianess can only be specified once + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian big, dce" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, endian big" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 3933, oper +, rvalue myrvalue, result foo, endian small, dce" + ) + .is_err() + ); + } + + #[test] + fn test_parser_oper_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: 3933, + oper: ByteMathOperator::Addition, + rvalue_str: CString::new("myrvalue").unwrap().into_raw(), + rvalue: 0, + result: CString::new("foo").unwrap().into_raw(), + endian: ByteMathEndian::BigEndian, + base: BASE_DEFAULT, + flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR, + ..Default::default() + }; + + match parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.oper = ByteMathOperator::Subtraction; + match parse_bytemath("bytes 4, offset 3933, oper -, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.oper = ByteMathOperator::Multiplication; + match parse_bytemath("bytes 4, offset 3933, oper *, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + bmd.oper = ByteMathOperator::Division; + match parse_bytemath("bytes 4, offset 3933, oper /, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + bmd.oper = ByteMathOperator::RightShift; + match parse_bytemath("bytes 4, offset 3933, oper >>, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + bmd.oper = ByteMathOperator::LeftShift; + match parse_bytemath("bytes 4, offset 3933, oper <<, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + fn test_parser_oper_invalid() { + assert!( + parse_bytemath("bytes 4, offset 0, oper !, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper ^, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper <>, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper ><, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper <, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper >, rvalue myvalue, result foo").is_err() + ); + } + + #[test] + fn test_parser_rvalue_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: 47303, + oper: ByteMathOperator::Multiplication, + rvalue_str: std::ptr::null_mut(), + rvalue: 4294967295, + result: CString::new("foo").unwrap().into_raw(), + endian: DETECT_BYTEMATH_ENDIAN_DEFAULT, + base: BASE_DEFAULT, + ..Default::default() + }; + + match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967295 , result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.rvalue = 1; + match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 1, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + bmd.rvalue = 0; + match parse_bytemath("bytes 4, offset 47303, oper *, rvalue 0, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + fn test_parser_rvalue_invalid() { + assert!( + parse_bytemath("bytes 4, offset 47303, oper *, rvalue 4294967296, result foo").is_err() + ); + } + + #[test] + fn test_parser_offset_valid() { + let mut bmd = DetectByteMathData { + nbytes: 4, + offset: -65535, + oper: ByteMathOperator::Multiplication, + rvalue_str: CString::new("myrvalue").unwrap().into_raw(), + rvalue: 0, + result: CString::new("foo").unwrap().into_raw(), + endian: DETECT_BYTEMATH_ENDIAN_DEFAULT, + base: BASE_DEFAULT, + flags: DETECT_BYTEMATH_FLAG_RVALUE_VAR, + ..Default::default() + }; + + match parse_bytemath("bytes 4, offset -65535, oper *, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + + bmd.offset = 65535; + match parse_bytemath("bytes 4, offset 65535, oper *, rvalue myrvalue, result foo") { + Ok((_, val)) => { + assert_eq!(val, bmd); + } + Err(_) => { + assert!(false); + } + } + } + + #[test] + // offset: numeric values must be between -65535 and 65535 + fn test_parser_offset_invalid() { + assert!( + parse_bytemath("bytes 4, offset -70000, oper *, rvalue myvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 70000, oper +, rvalue myvalue, result foo").is_err() + ); + } + + #[test] + fn test_parser_incomplete_args() { + assert!(parse_bytemath("").is_err()); + assert!(parse_bytemath("bytes 4").is_err()); + assert!(parse_bytemath("bytes 4, offset 0").is_err()); + assert!(parse_bytemath("bytes 4, offset 0, oper <<").is_err()); + } + + #[test] + fn test_parser_missing_required() { + assert!( + parse_bytemath("endian big, offset 3933, oper +, rvalue myrvalue, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, endian big, oper +, rvalue myrvalue, result foo,").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 3933, endian big, rvalue myrvalue, result foo") + .is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 3933, oper +, endian big, result foo").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 3933, oper +, rvalue myrvalue, endian big").is_err() + ); + } + + #[test] + fn test_parser_invalid_args() { + assert!(parse_bytemath("monkey banana").is_err()); + assert!(parse_bytemath("bytes nan").is_err()); + assert!(parse_bytemath("bytes 4, offset nan").is_err()); + assert!(parse_bytemath("bytes 4, offset 0, three 3, four 4, five 5, six 6, seven 7, eight 8, nine 9, ten 10, eleven 11").is_err()); + assert!( + parse_bytemath("bytes 4, offset 0, oper ><, rvalue myrvalue").is_err() + ); + assert!( + parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, endian endian").is_err() + ); + } + #[test] + fn test_parser_multiple() { + assert!( + parse_bytemath( + "bytes 4, bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 0, offset 0, oper +, rvalue myrvalue, result myresult, endian big" + ) + .is_err() + ); + assert!( + parse_bytemath( + "bytes 4, offset 0, oper +, oper +, rvalue myrvalue, result myresult, endian big" + ) + .is_err() + ); + assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, rvalue myrvalue, result myresult, endian big").is_err()); + assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, result myresult, endian big").is_err()); + assert!(parse_bytemath("bytes 4, offset 0, oper +, rvalue myrvalue, result myresult, endian big, endian big").is_err()); + } +} diff --git a/rust/src/detect/error.rs b/rust/src/detect/error.rs new file mode 100644 index 0000000..4959e2c --- /dev/null +++ b/rust/src/detect/error.rs @@ -0,0 +1,37 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::error::{ErrorKind, ParseError}; + +/// Custom rule parse errors. +/// +/// Implemented based on the Nom example for implementing custom errors. +#[derive(Debug, PartialEq, Eq)] +pub enum RuleParseError<I> { + InvalidByteMath(String), + + Nom(I, ErrorKind), +} +impl<I> ParseError<I> for RuleParseError<I> { + fn from_error_kind(input: I, kind: ErrorKind) -> Self { + RuleParseError::Nom(input, kind) + } + + fn append(_: I, _: ErrorKind, other: Self) -> Self { + other + } +} diff --git a/rust/src/detect/iprep.rs b/rust/src/detect/iprep.rs new file mode 100644 index 0000000..16f5d9d --- /dev/null +++ b/rust/src/detect/iprep.rs @@ -0,0 +1,128 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::uint::*; +use nom7::bytes::complete::{is_a, take_while}; +use nom7::character::complete::{alpha0, char, digit1}; +use nom7::combinator::{all_consuming, map_opt, map_res, opt}; +use nom7::error::{make_error, ErrorKind}; +use nom7::Err; +use nom7::IResult; + +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::str::FromStr; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum DetectIPRepDataCmd { + IPRepCmdAny = 0, + IPRepCmdBoth = 1, + IPRepCmdSrc = 2, + IPRepCmdDst = 3, +} + +impl std::str::FromStr for DetectIPRepDataCmd { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "any" => Ok(DetectIPRepDataCmd::IPRepCmdAny), + "both" => Ok(DetectIPRepDataCmd::IPRepCmdBoth), + "src" => Ok(DetectIPRepDataCmd::IPRepCmdSrc), + "dst" => Ok(DetectIPRepDataCmd::IPRepCmdDst), + _ => Err(format!( + "'{}' is not a valid value for DetectIPRepDataCmd", + s + )), + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct DetectIPRepData { + pub du8: DetectUintData<u8>, + pub cat: u8, + pub cmd: DetectIPRepDataCmd, +} + +pub fn is_alphanumeric_or_slash(chr: char) -> bool { + if chr.is_ascii_alphanumeric() { + return true; + } + if chr == '_' || chr == '-' { + return true; + } + return false; +} + +extern "C" { + pub fn SRepCatGetByShortname(name: *const c_char) -> u8; +} + +pub fn detect_parse_iprep(i: &str) -> IResult<&str, DetectIPRepData> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, cmd) = map_res(alpha0, DetectIPRepDataCmd::from_str)(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + + let (i, name) = take_while(is_alphanumeric_or_slash)(i)?; + // copy as to have final zero + let namez = CString::new(name).unwrap(); + let cat = unsafe { SRepCatGetByShortname(namez.as_ptr()) }; + if cat == 0 { + return Err(Err::Error(make_error(i, ErrorKind::MapOpt))); + } + + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, mode) = detect_parse_uint_mode(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg1) = map_opt(digit1, |s: &str| s.parse::<u8>().ok())(i)?; + let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?; + let du8 = DetectUintData::<u8> { + arg1, + arg2: 0, + mode, + }; + return Ok((i, DetectIPRepData { du8, cat, cmd })); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_iprep_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectIPRepData { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_iprep(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_iprep_free(ctx: &mut DetectIPRepData) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} diff --git a/rust/src/detect/mod.rs b/rust/src/detect/mod.rs new file mode 100644 index 0000000..d33c9ae --- /dev/null +++ b/rust/src/detect/mod.rs @@ -0,0 +1,27 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Module for rule parsing. + +pub mod byte_math; +pub mod error; +pub mod iprep; +pub mod parser; +pub mod stream_size; +pub mod uint; +pub mod uri; +pub mod requires; diff --git a/rust/src/detect/parser.rs b/rust/src/detect/parser.rs new file mode 100644 index 0000000..0ac5846 --- /dev/null +++ b/rust/src/detect/parser.rs @@ -0,0 +1,38 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::detect::error::RuleParseError; + +use nom7::bytes::complete::is_not; +use nom7::character::complete::multispace0; +use nom7::sequence::preceded; +use nom7::IResult; + +static WHITESPACE: &str = " \t\r\n"; +/// Parse all characters up until the next whitespace character. +pub fn take_until_whitespace(input: &str) -> IResult<&str, &str, RuleParseError<&str>> { + nom7::bytes::complete::is_not(WHITESPACE)(input) +} + +/// Parse the next token ignoring leading whitespace. +/// +/// A token is the next sequence of chars until a terminating character. Leading whitespace +/// is ignore. +pub fn parse_token(input: &str) -> IResult<&str, &str, RuleParseError<&str>> { + let terminators = "\n\r\t,;: "; + preceded(multispace0, is_not(terminators))(input) +} diff --git a/rust/src/detect/requires.rs b/rust/src/detect/requires.rs new file mode 100644 index 0000000..e9e1aca --- /dev/null +++ b/rust/src/detect/requires.rs @@ -0,0 +1,805 @@ +/* Copyright (C) 2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::collections::{HashSet, VecDeque}; +use std::{cmp::Ordering, ffi::CStr}; + +// std::ffi::{c_char, c_int} is recommended these days, but requires +// Rust 1.64.0. +use std::os::raw::{c_char, c_int}; + +use nom7::bytes::complete::take_while; +use nom7::combinator::map; +use nom7::multi::{many1, separated_list1}; +use nom7::sequence::tuple; +use nom7::{ + branch::alt, + bytes::complete::{tag, take_till}, + character::complete::{char, multispace0}, + combinator::map_res, + sequence::preceded, + IResult, +}; + +#[derive(Debug, Eq, PartialEq)] +enum RequiresError { + /// Suricata is greater than the required version. + VersionGt, + + /// Suricata is less than the required version. + VersionLt(SuricataVersion), + + /// The running Suricata is missing a required feature. + MissingFeature(String), + + /// The Suricata version, of Suricata itself is bad and failed to parse. + BadSuricataVersion, + + /// The requires expression is bad and failed to parse. + BadRequires, + + /// MultipleVersions + MultipleVersions, + + /// Passed in requirements not a valid UTF-8 string. + Utf8Error, +} + +impl RequiresError { + /// Return a pointer to a C compatible constant error message. + const fn c_errmsg(&self) -> *const c_char { + let msg = match self { + Self::VersionGt => "Suricata version greater than required\0", + Self::VersionLt(_) => "Suricata version less than required\0", + Self::MissingFeature(_) => "Suricata missing a required feature\0", + Self::BadSuricataVersion => "Failed to parse running Suricata version\0", + Self::BadRequires => "Failed to parse requires expression\0", + Self::MultipleVersions => "Version may only be specified once\0", + Self::Utf8Error => "Requires expression is not valid UTF-8\0", + }; + msg.as_ptr() as *const c_char + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +enum VersionCompareOp { + Gt, + Gte, + Lt, + Lte, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +struct SuricataVersion { + major: u8, + minor: u8, + patch: u8, +} + +impl PartialOrd for SuricataVersion { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + +impl Ord for SuricataVersion { + fn cmp(&self, other: &Self) -> Ordering { + match self.major.cmp(&other.major) { + Ordering::Equal => match self.minor.cmp(&other.minor) { + Ordering::Equal => self.patch.cmp(&other.patch), + other => other, + }, + other => other, + } + } +} + +impl std::fmt::Display for SuricataVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}.{}.{}", self.major, self.minor, self.patch) + } +} + +impl SuricataVersion { + fn new(major: u8, minor: u8, patch: u8) -> Self { + Self { + major, + minor, + patch, + } + } +} + +/// Parse a version expression. +/// +/// Parse into a version expression into a nested array, for example: +/// +/// version: >= 7.0.3 < 8 | >= 8.0.3 +/// +/// would result in something like: +/// +/// [ +/// [{op: gte, version: 7.0.3}, {op:lt, version: 8}], +/// [{op: gte, version: 8.0.3}], +/// ] +fn parse_version_expression(input: &str) -> IResult<&str, Vec<Vec<RuleRequireVersion>>> { + let sep = preceded(multispace0, tag("|")); + let inner_parser = many1(tuple((parse_op, parse_version))); + let (input, versions) = separated_list1(sep, inner_parser)(input)?; + + let versions = versions + .into_iter() + .map(|versions| { + versions + .into_iter() + .map(|(op, version)| RuleRequireVersion { op, version }) + .collect() + }) + .collect(); + + Ok((input, versions)) +} + +#[derive(Debug, Eq, PartialEq)] +struct RuleRequireVersion { + pub op: VersionCompareOp, + pub version: SuricataVersion, +} + +#[derive(Debug, Default, Eq, PartialEq)] +struct Requires { + pub features: Vec<String>, + + /// The version expression. + /// + /// - All of the inner most must evaluate to true. + /// - To pass, any of the outer must be true. + pub version: Vec<Vec<RuleRequireVersion>>, +} + +fn parse_op(input: &str) -> IResult<&str, VersionCompareOp> { + preceded( + multispace0, + alt(( + map(tag(">="), |_| VersionCompareOp::Gte), + map(tag(">"), |_| VersionCompareOp::Gt), + map(tag("<="), |_| VersionCompareOp::Lte), + map(tag("<"), |_| VersionCompareOp::Lt), + )), + )(input) +} + +/// Parse the next part of the version. +/// +/// That is all chars up to eof, or the next '.' or '-'. +fn parse_next_version_part(input: &str) -> IResult<&str, u8> { + map_res( + take_till(|c| c == '.' || c == '-' || c == ' '), + |s: &str| s.parse::<u8>(), + )(input) +} + +/// Parse a version string into a SuricataVersion. +fn parse_version(input: &str) -> IResult<&str, SuricataVersion> { + let (input, major) = preceded(multispace0, parse_next_version_part)(input)?; + let (input, minor) = if input.is_empty() || input.starts_with(' ') { + (input, 0) + } else { + preceded(char('.'), parse_next_version_part)(input)? + }; + let (input, patch) = if input.is_empty() || input.starts_with(' ') { + (input, 0) + } else { + preceded(char('.'), parse_next_version_part)(input)? + }; + + Ok((input, SuricataVersion::new(major, minor, patch))) +} + +fn parse_key_value(input: &str) -> IResult<&str, (&str, &str)> { + // Parse the keyword, any sequence of characters, numbers or "-" or "_". + let (input, key) = preceded( + multispace0, + take_while(|c: char| c.is_alphanumeric() || c == '-' || c == '_'), + )(input)?; + let (input, value) = preceded(multispace0, take_till(|c: char| c == ','))(input)?; + Ok((input, (key, value))) +} + +fn parse_requires(mut input: &str) -> Result<Requires, RequiresError> { + let mut requires = Requires::default(); + + while !input.is_empty() { + let (rest, (keyword, value)) = + parse_key_value(input).map_err(|_| RequiresError::BadRequires)?; + match keyword { + "feature" => { + requires.features.push(value.trim().to_string()); + } + "version" => { + if !requires.version.is_empty() { + return Err(RequiresError::MultipleVersions); + } + let (_, versions) = + parse_version_expression(value).map_err(|_| RequiresError::BadRequires)?; + requires.version = versions; + } + _ => { + // Unknown keyword, allow by warn in case we extend + // this in the future. + SCLogWarning!("Unknown requires keyword: {}", keyword); + } + } + + // No consume any remaining ',' or whitespace. + input = rest.trim_start_matches(|c: char| c == ',' || c.is_whitespace()); + } + Ok(requires) +} + +fn parse_suricata_version(version: &CStr) -> Result<SuricataVersion, *const c_char> { + let version = version + .to_str() + .map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?; + let (_, version) = + parse_version(version).map_err(|_| RequiresError::BadSuricataVersion.c_errmsg())?; + Ok(version) +} + +fn check_version( + version: &RuleRequireVersion, suricata_version: &SuricataVersion, +) -> Result<(), RequiresError> { + match version.op { + VersionCompareOp::Gt => { + if suricata_version <= &version.version { + return Err(RequiresError::VersionLt(version.version.clone())); + } + } + VersionCompareOp::Gte => { + if suricata_version < &version.version { + return Err(RequiresError::VersionLt(version.version.clone())); + } + } + VersionCompareOp::Lt => { + if suricata_version >= &version.version { + return Err(RequiresError::VersionGt); + } + } + VersionCompareOp::Lte => { + if suricata_version > &version.version { + return Err(RequiresError::VersionGt); + } + } + } + Ok(()) +} + +fn check_requires( + requires: &Requires, suricata_version: &SuricataVersion, +) -> Result<(), RequiresError> { + if !requires.version.is_empty() { + let mut errs = VecDeque::new(); + let mut ok = 0; + for or_versions in &requires.version { + let mut err = None; + for version in or_versions { + if let Err(_err) = check_version(version, suricata_version) { + err = Some(_err); + break; + } + } + if let Some(err) = err { + errs.push_back(err); + } else { + ok += 1; + } + } + if ok == 0 { + return Err(errs.pop_front().unwrap()); + } + } + + for feature in &requires.features { + if !crate::feature::requires(feature) { + return Err(RequiresError::MissingFeature(feature.to_string())); + } + } + + Ok(()) +} + +/// Status object to hold required features and the latest version of +/// Suricata required. +/// +/// Full qualified name as it is exposed to C. +#[derive(Debug, Default)] +pub struct SCDetectRequiresStatus { + min_version: Option<SuricataVersion>, + features: HashSet<String>, + + /// Number of rules that didn't meet a feature. + feature_count: u64, + + /// Number of rules where the Suricata version wasn't new enough. + lt_count: u64, + + /// Number of rules where the Suricata version was too new. + gt_count: u64, +} + +#[no_mangle] +pub extern "C" fn SCDetectRequiresStatusNew() -> *mut SCDetectRequiresStatus { + Box::into_raw(Box::default()) +} + +#[no_mangle] +pub unsafe extern "C" fn SCDetectRequiresStatusFree(status: *mut SCDetectRequiresStatus) { + if !status.is_null() { + std::mem::drop(Box::from_raw(status)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn SCDetectRequiresStatusLog( + status: &mut SCDetectRequiresStatus, suricata_version: *const c_char, tenant_id: u32, +) { + let suricata_version = CStr::from_ptr(suricata_version) + .to_str() + .unwrap_or("<unknown>"); + + let mut parts = vec![]; + if status.lt_count > 0 { + let min_version = status + .min_version + .as_ref() + .map(|v| v.to_string()) + .unwrap_or_else(|| "<unknown>".to_string()); + let msg = format!( + "{} {} skipped because the running Suricata version {} is less than {}", + status.lt_count, + if status.lt_count > 1 { + "rules were" + } else { + "rule was" + }, + suricata_version, + &min_version + ); + parts.push(msg); + } + if status.gt_count > 0 { + let msg = format!( + "{} {} for an older version Suricata", + status.gt_count, + if status.gt_count > 1 { + "rules were skipped as they are" + } else { + "rule was skipped as it is" + } + ); + parts.push(msg); + } + if status.feature_count > 0 { + let features = status + .features + .iter() + .map(|f| f.to_string()) + .collect::<Vec<String>>() + .join(", "); + let msg = format!( + "{}{} {} skipped because the running Suricata version does not have feature{}: [{}]", + if tenant_id > 0 { + format!("tenant id: {} ", tenant_id) + } else { + String::new() + }, + status.feature_count, + if status.feature_count > 1 { + "rules were" + } else { + "rule was" + }, + if status.feature_count > 1 { "s" } else { "" }, + &features + ); + parts.push(msg); + } + + let msg = parts.join("; "); + + if status.lt_count > 0 { + SCLogNotice!("{}", &msg); + } else if status.gt_count > 0 || status.feature_count > 0 { + SCLogInfo!("{}", &msg); + } +} + +/// Parse a "requires" rule option. +/// +/// Return values: +/// * 0 - OK, rule should continue loading +/// * -1 - Error parsing the requires content +/// * -4 - Requirements not met, don't continue loading the rule, this +/// value is chosen so it can be passed back to the options parser +/// as its treated as a non-fatal silent error. +#[no_mangle] +pub unsafe extern "C" fn SCDetectCheckRequires( + requires: *const c_char, suricata_version_string: *const c_char, errstr: *mut *const c_char, + status: &mut SCDetectRequiresStatus, +) -> c_int { + // First parse the running Suricata version. + let suricata_version = match parse_suricata_version(CStr::from_ptr(suricata_version_string)) { + Ok(version) => version, + Err(err) => { + *errstr = err; + return -1; + } + }; + + let requires = match CStr::from_ptr(requires) + .to_str() + .map_err(|_| RequiresError::Utf8Error) + .and_then(parse_requires) + { + Ok(requires) => requires, + Err(err) => { + *errstr = err.c_errmsg(); + return -1; + } + }; + + match check_requires(&requires, &suricata_version) { + Ok(()) => 0, + Err(err) => { + match &err { + RequiresError::VersionLt(version) => { + if let Some(min_version) = &status.min_version { + if version > min_version { + status.min_version = Some(version.clone()); + } + } else { + status.min_version = Some(version.clone()); + } + status.lt_count += 1; + } + RequiresError::MissingFeature(feature) => { + status.features.insert(feature.to_string()); + status.feature_count += 1; + } + RequiresError::VersionGt => { + status.gt_count += 1; + } + _ => {} + } + *errstr = err.c_errmsg(); + return -4; + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_suricata_version() { + // 7.1.1 < 7.1.2 + assert!(SuricataVersion::new(7, 1, 1) < SuricataVersion::new(7, 1, 2)); + + // 7.1.1 <= 7.1.2 + assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 2)); + + // 7.1.1 <= 7.1.1 + assert!(SuricataVersion::new(7, 1, 1) <= SuricataVersion::new(7, 1, 1)); + + // NOT 7.1.1 < 7.1.1 + assert!(SuricataVersion::new(7, 1, 1) >= SuricataVersion::new(7, 1, 1)); + + // 7.3.1 < 7.22.1 + assert!(SuricataVersion::new(7, 3, 1) < SuricataVersion::new(7, 22, 1)); + + // 7.22.1 >= 7.3.4 + assert!(SuricataVersion::new(7, 22, 1) >= SuricataVersion::new(7, 3, 4)); + } + + #[test] + fn test_parse_op() { + assert_eq!(parse_op(">").unwrap().1, VersionCompareOp::Gt); + assert_eq!(parse_op(">=").unwrap().1, VersionCompareOp::Gte); + assert_eq!(parse_op("<").unwrap().1, VersionCompareOp::Lt); + assert_eq!(parse_op("<=").unwrap().1, VersionCompareOp::Lte); + + assert!(parse_op("=").is_err()); + } + + #[test] + fn test_parse_version() { + assert_eq!( + parse_version("7").unwrap().1, + SuricataVersion { + major: 7, + minor: 0, + patch: 0, + } + ); + + assert_eq!( + parse_version("7.1").unwrap().1, + SuricataVersion { + major: 7, + minor: 1, + patch: 0, + } + ); + + assert_eq!( + parse_version("7.1.2").unwrap().1, + SuricataVersion { + major: 7, + minor: 1, + patch: 2, + } + ); + + // Suricata pre-releases will have a suffix starting with a + // '-', so make sure we accept those versions as well. + assert_eq!( + parse_version("8.0.0-dev").unwrap().1, + SuricataVersion { + major: 8, + minor: 0, + patch: 0, + } + ); + + assert!(parse_version("7.1.2a").is_err()); + assert!(parse_version("a").is_err()); + assert!(parse_version("777").is_err()); + assert!(parse_version("product-1").is_err()); + } + + #[test] + fn test_parse_requires() { + let requires = parse_requires(" feature geoip").unwrap(); + assert_eq!(&requires.features[0], "geoip"); + + let requires = parse_requires(" feature geoip, feature lua ").unwrap(); + assert_eq!(&requires.features[0], "geoip"); + assert_eq!(&requires.features[1], "lua"); + + let requires = parse_requires("version >=7").unwrap(); + assert_eq!( + requires, + Requires { + features: vec![], + version: vec![vec![RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 0, + patch: 0, + } + }]], + } + ); + + let requires = parse_requires("version >= 7.1").unwrap(); + assert_eq!( + requires, + Requires { + features: vec![], + version: vec![vec![RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 0, + } + }]], + } + ); + + let requires = parse_requires("feature output::file-store, version >= 7.1.2").unwrap(); + assert_eq!( + requires, + Requires { + features: vec!["output::file-store".to_string()], + version: vec![vec![RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 2, + } + }]], + } + ); + + let requires = parse_requires("feature geoip, version >= 7.1.2 < 8").unwrap(); + assert_eq!( + requires, + Requires { + features: vec!["geoip".to_string()], + version: vec![vec![ + RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 1, + patch: 2, + }, + }, + RuleRequireVersion { + op: VersionCompareOp::Lt, + version: SuricataVersion { + major: 8, + minor: 0, + patch: 0, + } + } + ]], + } + ); + } + + #[test] + fn test_check_requires() { + // Have 7.0.4, require >= 8. + let suricata_version = SuricataVersion::new(7, 0, 4); + let requires = parse_requires("version >= 8").unwrap(); + assert_eq!( + check_requires(&requires, &suricata_version), + Err(RequiresError::VersionLt(SuricataVersion { + major: 8, + minor: 0, + patch: 0, + })), + ); + + // Have 7.0.4, require 7.0.3. + let suricata_version = SuricataVersion::new(7, 0, 4); + let requires = parse_requires("version >= 7.0.3").unwrap(); + assert_eq!(check_requires(&requires, &suricata_version), Ok(())); + + // Have 8.0.0, require >= 7.0.0 and < 8.0 + let suricata_version = SuricataVersion::new(8, 0, 0); + let requires = parse_requires("version >= 7.0.0 < 8").unwrap(); + assert_eq!( + check_requires(&requires, &suricata_version), + Err(RequiresError::VersionGt) + ); + + // Have 8.0.0, require >= 7.0.0 and < 9.0 + let suricata_version = SuricataVersion::new(8, 0, 0); + let requires = parse_requires("version >= 7.0.0 < 9").unwrap(); + assert_eq!(check_requires(&requires, &suricata_version), Ok(())); + + // Require feature foobar. + let suricata_version = SuricataVersion::new(8, 0, 0); + let requires = parse_requires("feature foobar").unwrap(); + assert_eq!( + check_requires(&requires, &suricata_version), + Err(RequiresError::MissingFeature("foobar".to_string())) + ); + + // Require feature foobar, but this time we have the feature. + let suricata_version = SuricataVersion::new(8, 0, 0); + let requires = parse_requires("feature true_foobar").unwrap(); + assert_eq!(check_requires(&requires, &suricata_version), Ok(())); + + let suricata_version = SuricataVersion::new(8, 0, 1); + let requires = parse_requires("version >= 7.0.3 < 8").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_err()); + + let suricata_version = SuricataVersion::new(7, 0, 1); + let requires = parse_requires("version >= 7.0.3 < 8").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_err()); + + let suricata_version = SuricataVersion::new(7, 0, 3); + let requires = parse_requires("version >= 7.0.3 < 8").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_ok()); + + let suricata_version = SuricataVersion::new(8, 0, 3); + let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_ok()); + + let suricata_version = SuricataVersion::new(8, 0, 2); + let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_err()); + + let suricata_version = SuricataVersion::new(7, 0, 2); + let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_err()); + + let suricata_version = SuricataVersion::new(7, 0, 3); + let requires = parse_requires("version >= 7.0.3 < 8 | >= 8.0.3").unwrap(); + assert!(check_requires(&requires, &suricata_version).is_ok()); + + // Example of something that requires a fix/feature that was + // implemented in 7.0.5, 8.0.4, 9.0.3. + let requires = parse_requires("version >= 7.0.5 < 8 | >= 8.0.4 < 9 | >= 9.0.3").unwrap(); + assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 4)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 5)).is_ok()); + assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 3)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 4)).is_ok()); + assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 2)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 3)).is_ok()); + assert!(check_requires(&requires, &SuricataVersion::new(10, 0, 0)).is_ok()); + + let requires = parse_requires("version >= 8 < 9").unwrap(); + assert!(check_requires(&requires, &SuricataVersion::new(6, 0, 0)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(7, 0, 0)).is_err()); + assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_ok()); + assert!(check_requires(&requires, &SuricataVersion::new(9, 0, 0)).is_err()); + + // Unknown keyword. + let requires = parse_requires("feature lua, foo bar, version >= 7.0.3").unwrap(); + assert_eq!( + requires, + Requires { + features: vec!["lua".to_string()], + version: vec![vec![RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 0, + patch: 3, + } + }]], + } + ); + } + + #[test] + fn test_parse_version_expression() { + let version_str = ">= 7.0.3 < 8 | >= 8.0.3"; + let (rest, versions) = parse_version_expression(version_str).unwrap(); + assert!(rest.is_empty()); + assert_eq!( + versions, + vec![ + vec![ + RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 7, + minor: 0, + patch: 3, + } + }, + RuleRequireVersion { + op: VersionCompareOp::Lt, + version: SuricataVersion { + major: 8, + minor: 0, + patch: 0, + } + }, + ], + vec![RuleRequireVersion { + op: VersionCompareOp::Gte, + version: SuricataVersion { + major: 8, + minor: 0, + patch: 3, + } + },], + ] + ); + } +} diff --git a/rust/src/detect/stream_size.rs b/rust/src/detect/stream_size.rs new file mode 100644 index 0000000..cb8c826 --- /dev/null +++ b/rust/src/detect/stream_size.rs @@ -0,0 +1,98 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::uint::*; +use nom7::bytes::complete::{is_a, take_while}; +use nom7::character::complete::{alpha0, char, digit1}; +use nom7::combinator::{all_consuming, map_opt, map_res, opt}; +use nom7::IResult; + +use std::ffi::CStr; +use std::str::FromStr; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum DetectStreamSizeDataFlags { + StreamSizeServer = 1, + StreamSizeClient = 2, + StreamSizeBoth = 3, + StreamSizeEither = 4, +} + +impl std::str::FromStr for DetectStreamSizeDataFlags { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + match s { + "server" => Ok(DetectStreamSizeDataFlags::StreamSizeServer), + "client" => Ok(DetectStreamSizeDataFlags::StreamSizeClient), + "both" => Ok(DetectStreamSizeDataFlags::StreamSizeBoth), + "either" => Ok(DetectStreamSizeDataFlags::StreamSizeEither), + _ => Err(format!( + "'{}' is not a valid value for DetectStreamSizeDataFlags", + s + )), + } + } +} + +#[derive(Debug)] +#[repr(C)] +pub struct DetectStreamSizeData { + pub flags: DetectStreamSizeDataFlags, + pub du32: DetectUintData<u32>, +} + +pub fn detect_parse_stream_size(i: &str) -> IResult<&str, DetectStreamSizeData> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, flags) = map_res(alpha0, DetectStreamSizeDataFlags::from_str)(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, mode) = detect_parse_uint_mode(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg1) = map_opt(digit1, |s: &str| s.parse::<u32>().ok())(i)?; + let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?; + let du32 = DetectUintData::<u32> { + arg1, + arg2: 0, + mode, + }; + Ok((i, DetectStreamSizeData { flags, du32 })) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_stream_size_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectStreamSizeData { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_stream_size(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_stream_size_free(ctx: &mut DetectStreamSizeData) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} diff --git a/rust/src/detect/uint.rs b/rust/src/detect/uint.rs new file mode 100644 index 0000000..3d6a5ba --- /dev/null +++ b/rust/src/detect/uint.rs @@ -0,0 +1,435 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::branch::alt; +use nom7::bytes::complete::{is_a, tag, tag_no_case, take_while}; +use nom7::character::complete::digit1; +use nom7::combinator::{all_consuming, map_opt, opt, value, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::Err; +use nom7::IResult; + +use std::ffi::CStr; + +#[derive(PartialEq, Eq, Clone, Debug)] +#[repr(u8)] +pub enum DetectUintMode { + DetectUintModeEqual, + DetectUintModeLt, + DetectUintModeLte, + DetectUintModeGt, + DetectUintModeGte, + DetectUintModeRange, + DetectUintModeNe, +} + +#[derive(Debug)] +#[repr(C)] +pub struct DetectUintData<T> { + pub arg1: T, + pub arg2: T, + pub mode: DetectUintMode, +} + +pub trait DetectIntType: + std::str::FromStr + + std::cmp::PartialOrd + + num::PrimInt + + num::Bounded + + num::ToPrimitive + + num::FromPrimitive +{ +} +impl<T> DetectIntType for T where + T: std::str::FromStr + + std::cmp::PartialOrd + + num::PrimInt + + num::Bounded + + num::ToPrimitive + + num::FromPrimitive +{ +} + +pub fn detect_parse_uint_unit(i: &str) -> IResult<&str, u64> { + let (i, unit) = alt(( + value(1024, tag_no_case("kb")), + value(1024 * 1024, tag_no_case("mb")), + value(1024 * 1024 * 1024, tag_no_case("gb")), + ))(i)?; + return Ok((i, unit)); +} + +pub fn detect_parse_uint_with_unit<T: DetectIntType>(i: &str) -> IResult<&str, T> { + let (i, arg1) = map_opt(digit1, |s: &str| s.parse::<T>().ok())(i)?; + let (i, unit) = opt(detect_parse_uint_unit)(i)?; + if arg1 >= T::one() { + if let Some(u) = unit { + if T::max_value().to_u64().unwrap() / u < arg1.to_u64().unwrap() { + return Err(Err::Error(make_error(i, ErrorKind::Verify))); + } + let ru64 = arg1 * T::from_u64(u).unwrap(); + return Ok((i, ru64)); + } + } + Ok((i, arg1)) +} + +pub fn detect_parse_uint_start_equal<T: DetectIntType>( + i: &str, +) -> IResult<&str, DetectUintData<T>> { + let (i, _) = opt(tag("="))(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg1) = detect_parse_uint_with_unit(i)?; + Ok(( + i, + DetectUintData { + arg1, + arg2: T::min_value(), + mode: DetectUintMode::DetectUintModeEqual, + }, + )) +} + +pub fn detect_parse_uint_start_interval<T: DetectIntType>( + i: &str, +) -> IResult<&str, DetectUintData<T>> { + let (i, arg1) = map_opt(digit1, |s: &str| s.parse::<T>().ok())(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = alt((tag("-"), tag("<>")))(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg2) = verify(map_opt(digit1, |s: &str| s.parse::<T>().ok()), |x| { + x > &arg1 && *x - arg1 > T::one() + })(i)?; + Ok(( + i, + DetectUintData { + arg1, + arg2, + mode: DetectUintMode::DetectUintModeRange, + }, + )) +} + +fn detect_parse_uint_start_interval_inclusive<T: DetectIntType>( + i: &str, +) -> IResult<&str, DetectUintData<T>> { + let (i, arg1) = verify(map_opt(digit1, |s: &str| s.parse::<T>().ok()), |x| { + *x > T::min_value() + })(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = alt((tag("-"), tag("<>")))(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg2) = verify(map_opt(digit1, |s: &str| s.parse::<T>().ok()), |x| { + *x > arg1 && *x < T::max_value() + })(i)?; + Ok(( + i, + DetectUintData { + arg1: arg1 - T::one(), + arg2: arg2 + T::one(), + mode: DetectUintMode::DetectUintModeRange, + }, + )) +} + +pub fn detect_parse_uint_mode(i: &str) -> IResult<&str, DetectUintMode> { + let (i, mode) = alt(( + value(DetectUintMode::DetectUintModeGte, tag(">=")), + value(DetectUintMode::DetectUintModeLte, tag("<=")), + value(DetectUintMode::DetectUintModeGt, tag(">")), + value(DetectUintMode::DetectUintModeLt, tag("<")), + value(DetectUintMode::DetectUintModeNe, tag("!=")), + value(DetectUintMode::DetectUintModeNe, tag("!")), + value(DetectUintMode::DetectUintModeEqual, tag("=")), + ))(i)?; + return Ok((i, mode)); +} + +fn detect_parse_uint_start_symbol<T: DetectIntType>(i: &str) -> IResult<&str, DetectUintData<T>> { + let (i, mode) = detect_parse_uint_mode(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, arg1) = map_opt(digit1, |s: &str| s.parse::<T>().ok())(i)?; + + match mode { + DetectUintMode::DetectUintModeNe => {} + DetectUintMode::DetectUintModeLt => { + if arg1 == T::min_value() { + return Err(Err::Error(make_error(i, ErrorKind::Verify))); + } + } + DetectUintMode::DetectUintModeLte => { + if arg1 == T::max_value() { + return Err(Err::Error(make_error(i, ErrorKind::Verify))); + } + } + DetectUintMode::DetectUintModeGt => { + if arg1 == T::max_value() { + return Err(Err::Error(make_error(i, ErrorKind::Verify))); + } + } + DetectUintMode::DetectUintModeGte => { + if arg1 == T::min_value() { + return Err(Err::Error(make_error(i, ErrorKind::Verify))); + } + } + _ => { + return Err(Err::Error(make_error(i, ErrorKind::MapOpt))); + } + } + + Ok(( + i, + DetectUintData { + arg1, + arg2: T::min_value(), + mode, + }, + )) +} + +pub fn detect_match_uint<T: DetectIntType>(x: &DetectUintData<T>, val: T) -> bool { + match x.mode { + DetectUintMode::DetectUintModeEqual => { + if val == x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeNe => { + if val != x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeLt => { + if val < x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeLte => { + if val <= x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeGt => { + if val > x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeGte => { + if val >= x.arg1 { + return true; + } + } + DetectUintMode::DetectUintModeRange => { + if val > x.arg1 && val < x.arg2 { + return true; + } + } + } + return false; +} + +pub fn detect_parse_uint_notending<T: DetectIntType>(i: &str) -> IResult<&str, DetectUintData<T>> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, uint) = alt(( + detect_parse_uint_start_interval, + detect_parse_uint_start_equal, + detect_parse_uint_start_symbol, + ))(i)?; + Ok((i, uint)) +} + +pub fn detect_parse_uint<T: DetectIntType>(i: &str) -> IResult<&str, DetectUintData<T>> { + let (i, uint) = detect_parse_uint_notending(i)?; + let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?; + Ok((i, uint)) +} + +pub fn detect_parse_uint_inclusive<T: DetectIntType>(i: &str) -> IResult<&str, DetectUintData<T>> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, uint) = alt(( + detect_parse_uint_start_interval_inclusive, + detect_parse_uint_start_equal, + detect_parse_uint_start_symbol, + ))(i)?; + let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?; + Ok((i, uint)) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u64_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData<u64> { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_uint::<u64>(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u64_match( + arg: u64, ctx: &DetectUintData<u64>, +) -> std::os::raw::c_int { + if detect_match_uint(ctx, arg) { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u64_free(ctx: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx as *mut DetectUintData<u64>)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u32_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData<u32> { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_uint::<u32>(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u32_parse_inclusive( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData<u32> { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_uint_inclusive::<u32>(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u32_match( + arg: u32, ctx: &DetectUintData<u32>, +) -> std::os::raw::c_int { + if detect_match_uint(ctx, arg) { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u32_free(ctx: &mut DetectUintData<u32>) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u8_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData<u8> { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_uint::<u8>(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u8_match( + arg: u8, ctx: &DetectUintData<u8>, +) -> std::os::raw::c_int { + if detect_match_uint(ctx, arg) { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u8_free(ctx: &mut DetectUintData<u8>) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u16_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUintData<u16> { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_uint::<u16>(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u16_match( + arg: u16, ctx: &DetectUintData<u16>, +) -> std::os::raw::c_int { + if detect_match_uint(ctx, arg) { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_u16_free(ctx: &mut DetectUintData<u16>) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_uint_unit() { + match detect_parse_uint::<u64>(" 2kb") { + Ok((_, val)) => { + assert_eq!(val.arg1, 2048); + } + Err(_) => { + assert!(false); + } + } + match detect_parse_uint::<u8>("2kb") { + Ok((_, _val)) => { + assert!(false); + } + Err(_) => {} + } + match detect_parse_uint::<u32>("3MB") { + Ok((_, val)) => { + assert_eq!(val.arg1, 3 * 1024 * 1024); + } + Err(_) => { + assert!(false); + } + } + } +} diff --git a/rust/src/detect/uri.rs b/rust/src/detect/uri.rs new file mode 100644 index 0000000..ae98278 --- /dev/null +++ b/rust/src/detect/uri.rs @@ -0,0 +1,78 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::uint::*; +use nom7::branch::alt; +use nom7::bytes::complete::{is_a, tag}; +use nom7::character::complete::char; +use nom7::combinator::{opt, value}; +use nom7::IResult; + +use std::ffi::CStr; + +#[derive(Debug)] +#[repr(C)] +pub struct DetectUrilenData { + pub du16: DetectUintData<u16>, + pub raw_buffer: bool, +} + +pub fn detect_parse_urilen_raw(i: &str) -> IResult<&str, bool> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = char(',')(i)?; + let (i, _) = opt(is_a(" "))(i)?; + return alt((value(true, tag("raw")), value(false, tag("norm"))))(i); +} + +pub fn detect_parse_urilen(i: &str) -> IResult<&str, DetectUrilenData> { + let (i, du16) = detect_parse_uint_notending::<u16>(i)?; + let (i, raw) = opt(detect_parse_urilen_raw)(i)?; + match raw { + Some(raw_buffer) => { + return Ok((i, DetectUrilenData { du16, raw_buffer })); + } + None => { + return Ok(( + i, + DetectUrilenData { + du16, + raw_buffer: false, + }, + )); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_urilen_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectUrilenData { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_urilen(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_urilen_free(ctx: &mut DetectUrilenData) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} diff --git a/rust/src/dhcp/README.txt b/rust/src/dhcp/README.txt new file mode 100644 index 0000000..fb7fcc4 --- /dev/null +++ b/rust/src/dhcp/README.txt @@ -0,0 +1,4 @@ +These test pcap files are individual packets broken out of a pcap +containing 4 DHCP messages. The original source of the PCAP file is + +https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=get&target=dhcp.pcap diff --git a/rust/src/dhcp/ack.pcap b/rust/src/dhcp/ack.pcap Binary files differnew file mode 100644 index 0000000..3c144dc --- /dev/null +++ b/rust/src/dhcp/ack.pcap diff --git a/rust/src/dhcp/detect.rs b/rust/src/dhcp/detect.rs new file mode 100644 index 0000000..0215810 --- /dev/null +++ b/rust/src/dhcp/detect.rs @@ -0,0 +1,66 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::dhcp::{ + DHCPTransaction, DHCP_OPT_ADDRESS_TIME, DHCP_OPT_REBINDING_TIME, DHCP_OPT_RENEWAL_TIME, +}; +use super::parser::DHCPOptionWrapper; + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_tx_get_leasetime( + tx: &mut DHCPTransaction, leasetime: *mut u64, +) -> u8 { + for option in &tx.message.options { + if option.code == DHCP_OPT_ADDRESS_TIME { + if let DHCPOptionWrapper::TimeValue(ref time_value) = option.option { + *leasetime = time_value.seconds as u64; + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_tx_get_rebinding_time( + tx: &mut DHCPTransaction, res: *mut u64, +) -> u8 { + for option in &tx.message.options { + if option.code == DHCP_OPT_REBINDING_TIME { + if let DHCPOptionWrapper::TimeValue(ref time_value) = option.option { + *res = time_value.seconds as u64; + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_tx_get_renewal_time( + tx: &mut DHCPTransaction, res: *mut u64, +) -> u8 { + for option in &tx.message.options { + if option.code == DHCP_OPT_RENEWAL_TIME { + if let DHCPOptionWrapper::TimeValue(ref time_value) = option.option { + *res = time_value.seconds as u64; + return 1; + } + } + } + return 0; +} diff --git a/rust/src/dhcp/dhcp.rs b/rust/src/dhcp/dhcp.rs new file mode 100644 index 0000000..b69b675 --- /dev/null +++ b/rust/src/dhcp/dhcp.rs @@ -0,0 +1,317 @@ +/* Copyright (C) 2018-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::applayer::{self, *}; +use crate::core; +use crate::core::{ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_UDP}; +use crate::dhcp::parser::*; +use std; +use std::ffi::CString; + +static mut ALPROTO_DHCP: AppProto = ALPROTO_UNKNOWN; + +static DHCP_MIN_FRAME_LEN: u32 = 232; + +pub const BOOTP_REQUEST: u8 = 1; +pub const BOOTP_REPLY: u8 = 2; + +// DHCP option types. Names based on IANA naming: +// https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml +pub const DHCP_OPT_SUBNET_MASK: u8 = 1; +pub const DHCP_OPT_ROUTERS: u8 = 3; +pub const DHCP_OPT_DNS_SERVER: u8 = 6; +pub const DHCP_OPT_HOSTNAME: u8 = 12; +pub const DHCP_OPT_REQUESTED_IP: u8 = 50; +pub const DHCP_OPT_ADDRESS_TIME: u8 = 51; +pub const DHCP_OPT_TYPE: u8 = 53; +//unused pub const DHCP_OPT_SERVER_ID: u8 = 54; +pub const DHCP_OPT_PARAMETER_LIST: u8 = 55; +pub const DHCP_OPT_RENEWAL_TIME: u8 = 58; +pub const DHCP_OPT_REBINDING_TIME: u8 = 59; +pub const DHCP_OPT_VENDOR_CLASS_ID: u8 = 60; +pub const DHCP_OPT_CLIENT_ID: u8 = 61; +pub const DHCP_OPT_END: u8 = 255; + +/// DHCP message types. +pub const DHCP_TYPE_DISCOVER: u8 = 1; +pub const DHCP_TYPE_OFFER: u8 = 2; +pub const DHCP_TYPE_REQUEST: u8 = 3; +pub const DHCP_TYPE_DECLINE: u8 = 4; +pub const DHCP_TYPE_ACK: u8 = 5; +pub const DHCP_TYPE_NAK: u8 = 6; +pub const DHCP_TYPE_RELEASE: u8 = 7; +pub const DHCP_TYPE_INFORM: u8 = 8; + +// DHCP parameter types. +// https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.txt +pub const DHCP_PARAM_SUBNET_MASK: u8 = 1; +pub const DHCP_PARAM_ROUTER: u8 = 3; +pub const DHCP_PARAM_DNS_SERVER: u8 = 6; +pub const DHCP_PARAM_DOMAIN: u8 = 15; +pub const DHCP_PARAM_ARP_TIMEOUT: u8 = 35; +pub const DHCP_PARAM_NTP_SERVER: u8 = 42; +pub const DHCP_PARAM_TFTP_SERVER_NAME: u8 = 66; +pub const DHCP_PARAM_TFTP_SERVER_IP: u8 = 150; + +#[derive(AppLayerEvent)] +pub enum DHCPEvent { + TruncatedOptions, + MalformedOptions, +} + +/// The concept of a transaction is more to satisfy the Suricata +/// app-layer. This DHCP parser is actually stateless where each +/// message is its own transaction. +pub struct DHCPTransaction { + tx_id: u64, + pub message: DHCPMessage, + tx_data: applayer::AppLayerTxData, +} + +impl DHCPTransaction { + pub fn new(id: u64, message: DHCPMessage) -> DHCPTransaction { + DHCPTransaction { + tx_id: id, + message, + tx_data: applayer::AppLayerTxData::new(), + } + } +} + +impl Transaction for DHCPTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +#[derive(Default)] +pub struct DHCPState { + state_data: AppLayerStateData, + + // Internal transaction ID. + tx_id: u64, + + // List of transactions. + transactions: Vec<DHCPTransaction>, + + events: u16, +} + +impl State<DHCPTransaction> for DHCPState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&DHCPTransaction> { + self.transactions.get(index) + } +} + +impl DHCPState { + pub fn new() -> Self { + Default::default() + } + + pub fn parse(&mut self, input: &[u8]) -> bool { + match dhcp_parse(input) { + Ok((_, message)) => { + let malformed_options = message.malformed_options; + let truncated_options = message.truncated_options; + self.tx_id += 1; + let transaction = DHCPTransaction::new(self.tx_id, message); + self.transactions.push(transaction); + if malformed_options { + self.set_event(DHCPEvent::MalformedOptions); + } + if truncated_options { + self.set_event(DHCPEvent::TruncatedOptions); + } + return true; + } + _ => { + return false; + } + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&DHCPTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + fn set_event(&mut self, event: DHCPEvent) { + if let Some(tx) = self.transactions.last_mut() { + tx.tx_data.set_event(event as u8); + self.events += 1; + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_probing_parser(_flow: *const Flow, + _direction: u8, + input: *const u8, + input_len: u32, + _rdir: *mut u8) -> AppProto +{ + if input_len < DHCP_MIN_FRAME_LEN { + return ALPROTO_UNKNOWN; + } + + let slice = build_slice!(input, input_len as usize); + match parse_header(slice) { + Ok((_, _)) => { + return ALPROTO_DHCP; + } + _ => { + return ALPROTO_UNKNOWN; + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dhcp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, + _direction: u8) -> std::os::raw::c_int { + // As this is a stateless parser, simply use 1. + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_state_get_tx(state: *mut std::os::raw::c_void, + tx_id: u64) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, DHCPState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, DHCPState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_parse(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state, DHCPState); + if state.parse(stream_slice.as_slice()) { + return AppLayerResult::ok(); + } + return AppLayerResult::err(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_state_tx_free( + state: *mut std::os::raw::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state, DHCPState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_dhcp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = DHCPState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut DHCPState)); +} + +export_tx_data_get!(rs_dhcp_get_tx_data, DHCPTransaction); +export_state_data_get!(rs_dhcp_get_state_data, DHCPState); + +const PARSER_NAME: &[u8] = b"dhcp\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_register_parser() { + SCLogDebug!("Registering DHCP parser."); + let ports = CString::new("[67,68]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port : ports.as_ptr(), + ipproto : IPPROTO_UDP, + probe_ts : Some(rs_dhcp_probing_parser), + probe_tc : Some(rs_dhcp_probing_parser), + min_depth : 0, + max_depth : 16, + state_new : rs_dhcp_state_new, + state_free : rs_dhcp_state_free, + tx_free : rs_dhcp_state_tx_free, + parse_ts : rs_dhcp_parse, + parse_tc : rs_dhcp_parse, + get_tx_count : rs_dhcp_state_get_tx_count, + get_tx : rs_dhcp_state_get_tx, + tx_comp_st_ts : 1, + tx_comp_st_tc : 1, + tx_get_progress : rs_dhcp_tx_get_alstate_progress, + get_eventinfo : Some(DHCPEvent::get_event_info), + get_eventinfo_byid : Some(DHCPEvent::get_event_info_by_id), + localstorage_new : None, + localstorage_free : None, + get_tx_files : None, + get_tx_iterator : Some(applayer::state_get_tx_iterator::<DHCPState, DHCPTransaction>), + get_tx_data : rs_dhcp_get_tx_data, + get_state_data : rs_dhcp_get_state_data, + apply_tx_config : None, + flags : 0, + truncate : None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DHCP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for DHCP."); + } +} diff --git a/rust/src/dhcp/discover.pcap b/rust/src/dhcp/discover.pcap Binary files differnew file mode 100644 index 0000000..f692f3f --- /dev/null +++ b/rust/src/dhcp/discover.pcap diff --git a/rust/src/dhcp/logger.rs b/rust/src/dhcp/logger.rs new file mode 100644 index 0000000..b29e215 --- /dev/null +++ b/rust/src/dhcp/logger.rs @@ -0,0 +1,286 @@ +/* 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. + */ + +use std; +use std::os::raw::c_void; + +use crate::dhcp::dhcp::*; +use crate::dhcp::parser::{DHCPOptionWrapper,DHCPOptGeneric}; +use crate::dns::log::dns_print_addr; +use crate::conf::ConfNode; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +pub struct DHCPLogger { + extended: bool, +} + +impl DHCPLogger { + + pub fn new(conf: ConfNode) -> Self { + return Self { + extended: conf.get_child_bool("extended"), + } + } + + fn get_type(&self, tx: &DHCPTransaction) -> Option<u8> { + let options = &tx.message.options; + for option in options { + let code = option.code; + #[allow(clippy::single_match)] + match &option.option { + DHCPOptionWrapper::Generic(option) => { + #[allow(clippy::single_match)] + match code { + DHCP_OPT_TYPE => { + if !option.data.is_empty() { + return Some(option.data[0]); + } + } + _ => {} + } + } + _ => {} + } + } + return None; + } + + pub fn do_log(&self, tx: &DHCPTransaction) -> bool { + if !self.extended { + if let Some(DHCP_TYPE_ACK) = self.get_type(tx){ + return true; + } + return false; + } + return true; + } + + pub fn log(&self, tx: &DHCPTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + let header = &tx.message.header; + let options = &tx.message.options; + + js.open_object("dhcp")?; + + match header.opcode { + BOOTP_REQUEST => { + js.set_string("type", "request")?; + } + BOOTP_REPLY => { + js.set_string("type", "reply")?; + } + _ => { + js.set_string("type", "<unknown>")?; + } + } + + js.set_uint("id", header.txid as u64)?; + js.set_string("client_mac", + &format_addr_hex(&header.clienthw.to_vec()))?; + js.set_string("assigned_ip", &dns_print_addr(&header.yourip))?; + + if self.extended { + js.set_string("client_ip", &dns_print_addr(&header.clientip))?; + if header.opcode == BOOTP_REPLY { + js.set_string("relay_ip", + &dns_print_addr(&header.giaddr))?; + js.set_string("next_server_ip", + &dns_print_addr(&header.serverip))?; + } + } + + for option in options { + let code = option.code; + match option.option { + DHCPOptionWrapper::ClientId(ref clientid) => { + js.set_string("client_id", + &format_addr_hex(&clientid.data))?; + } + DHCPOptionWrapper::TimeValue(ref time_value) => { + match code { + DHCP_OPT_ADDRESS_TIME => { + if self.extended { + js.set_uint("lease_time", + time_value.seconds as u64)?; + } + } + DHCP_OPT_REBINDING_TIME => { + if self.extended { + js.set_uint("rebinding_time", + time_value.seconds as u64)?; + } + } + DHCP_OPT_RENEWAL_TIME => { + js.set_uint("renewal_time", + time_value.seconds as u64)?; + } + _ => {} + } + } + DHCPOptionWrapper::Generic(ref option) => { + match code { + DHCP_OPT_SUBNET_MASK => { + if self.extended { + js.set_string("subnet_mask", + &dns_print_addr(&option.data))?; + } + } + DHCP_OPT_HOSTNAME => { + if !option.data.is_empty() { + js.set_string_from_bytes("hostname", + &option.data)?; + } + } + DHCP_OPT_TYPE => { + self.log_opt_type(js, option)?; + } + DHCP_OPT_REQUESTED_IP => { + if self.extended { + js.set_string("requested_ip", + &dns_print_addr(&option.data))?; + } + } + DHCP_OPT_PARAMETER_LIST => { + if self.extended { + self.log_opt_parameters(js, option)?; + } + } + DHCP_OPT_DNS_SERVER => { + if self.extended { + self.log_opt_dns_server(js, option)?; + } + } + DHCP_OPT_ROUTERS => { + if self.extended { + self.log_opt_routers(js, option)?; + } + } + DHCP_OPT_VENDOR_CLASS_ID => { + if self.extended && !option.data.is_empty(){ + js.set_string_from_bytes("vendor_class_identifier", + &option.data)?; + } + } + _ => {} + } + } + _ => {} + } + } + + js.close()?; + + return Ok(()); + } + + fn log_opt_type(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { + if !option.data.is_empty() { + let dhcp_type = match option.data[0] { + DHCP_TYPE_DISCOVER => "discover", + DHCP_TYPE_OFFER => "offer", + DHCP_TYPE_REQUEST => "request", + DHCP_TYPE_DECLINE => "decline", + DHCP_TYPE_ACK => "ack", + DHCP_TYPE_NAK => "nak", + DHCP_TYPE_RELEASE => "release", + DHCP_TYPE_INFORM => "inform", + _ => "unknown" + }; + js.set_string("dhcp_type", dhcp_type)?; + } + Ok(()) + } + + fn log_opt_parameters(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { + js.open_array("params")?; + for i in &option.data { + let param = match *i { + DHCP_PARAM_SUBNET_MASK => "subnet_mask", + DHCP_PARAM_ROUTER => "router", + DHCP_PARAM_DNS_SERVER => "dns_server", + DHCP_PARAM_DOMAIN => "domain", + DHCP_PARAM_ARP_TIMEOUT => "arp_timeout", + DHCP_PARAM_NTP_SERVER => "ntp_server", + DHCP_PARAM_TFTP_SERVER_NAME => "tftp_server_name", + DHCP_PARAM_TFTP_SERVER_IP => "tftp_server_ip", + _ => "" + }; + if !param.is_empty() { + js.append_string(param)?; + } + } + js.close()?; + Ok(()) + } + + fn log_opt_dns_server(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { + js.open_array("dns_servers")?; + for i in 0..(option.data.len() / 4) { + let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4].to_vec()); + js.append_string(&val)?; + } + js.close()?; + Ok(()) + } + + fn log_opt_routers(&self, js: &mut JsonBuilder, option: &DHCPOptGeneric) -> Result<(), JsonError> { + js.open_array("routers")?; + for i in 0..(option.data.len() / 4) { + let val = dns_print_addr(&option.data[(i * 4)..(i * 4) + 4].to_vec()); + js.append_string(&val)?; + } + js.close()?; + Ok(()) + } + +} + +fn format_addr_hex(input: &[u8]) -> String { + let parts: Vec<String> = input.iter() + .map(|b| format!("{:02x}", b)) + .collect(); + return parts.join(":"); +} + +#[no_mangle] +pub extern "C" fn rs_dhcp_logger_new(conf: *const c_void) -> *mut std::os::raw::c_void { + let conf = ConfNode::wrap(conf); + let boxed = Box::new(DHCPLogger::new(conf)); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_logger_free(logger: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(logger as *mut DHCPLogger)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_logger_log(logger: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, + js: &mut JsonBuilder) -> bool { + let logger = cast_pointer!(logger, DHCPLogger); + let tx = cast_pointer!(tx, DHCPTransaction); + logger.log(tx, js).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dhcp_logger_do_log(logger: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void) + -> bool { + let logger = cast_pointer!(logger, DHCPLogger); + let tx = cast_pointer!(tx, DHCPTransaction); + logger.do_log(tx) +} diff --git a/rust/src/dhcp/mod.rs b/rust/src/dhcp/mod.rs new file mode 100644 index 0000000..fd783d9 --- /dev/null +++ b/rust/src/dhcp/mod.rs @@ -0,0 +1,23 @@ +/* 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. + */ + +//! DHCP parser, detection and logger module. + +pub mod dhcp; +pub mod parser; +pub mod logger; +pub mod detect; diff --git a/rust/src/dhcp/offer.pcap b/rust/src/dhcp/offer.pcap Binary files differnew file mode 100644 index 0000000..9d23e25 --- /dev/null +++ b/rust/src/dhcp/offer.pcap diff --git a/rust/src/dhcp/parser.rs b/rust/src/dhcp/parser.rs new file mode 100644 index 0000000..48acccf --- /dev/null +++ b/rust/src/dhcp/parser.rs @@ -0,0 +1,317 @@ +/* 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. + */ + +use std::cmp::min; + +use crate::dhcp::dhcp::*; +use nom7::bytes::streaming::take; +use nom7::combinator::verify; +use nom7::number::streaming::{be_u16, be_u32, be_u8}; +use nom7::IResult; + +pub struct DHCPMessage { + pub header: DHCPHeader, + + pub options: Vec<DHCPOption>, + + // Set to true if the options were found to be malformed. That is + // failing to parse with enough data. + pub malformed_options: bool, + + // Set to true if the options failed to parse due to not enough + // data. + pub truncated_options: bool, +} + +pub struct DHCPHeader { + pub opcode: u8, + pub htype: u8, + pub hlen: u8, + pub hops: u8, + pub txid: u32, + pub seconds: u16, + pub flags: u16, + pub clientip: Vec<u8>, + pub yourip: Vec<u8>, + pub serverip: Vec<u8>, + pub giaddr: Vec<u8>, + pub clienthw: Vec<u8>, + pub servername: Vec<u8>, + pub bootfilename: Vec<u8>, + pub magic: Vec<u8>, +} + +pub struct DHCPOptClientId { + pub htype: u8, + pub data: Vec<u8>, +} + +/// Option type for time values. +pub struct DHCPOptTimeValue { + pub seconds: u32, +} + +pub struct DHCPOptGeneric { + pub data: Vec<u8>, +} + +pub enum DHCPOptionWrapper { + ClientId(DHCPOptClientId), + TimeValue(DHCPOptTimeValue), + Generic(DHCPOptGeneric), + End, +} + +pub struct DHCPOption { + pub code: u8, + pub data: Option<Vec<u8>>, + pub option: DHCPOptionWrapper, +} + +pub fn parse_header(i: &[u8]) -> IResult<&[u8], DHCPHeader> { + let (i, opcode) = be_u8(i)?; + let (i, htype) = be_u8(i)?; + let (i, hlen) = be_u8(i)?; + let (i, hops) = be_u8(i)?; + let (i, txid) = be_u32(i)?; + let (i, seconds) = be_u16(i)?; + let (i, flags) = be_u16(i)?; + let (i, clientip) = take(4_usize)(i)?; + let (i, yourip) = take(4_usize)(i)?; + let (i, serverip) = take(4_usize)(i)?; + let (i, giaddr) = take(4_usize)(i)?; + let (i, clienthw) = take(16_usize)(i)?; + let (i, servername) = take(64_usize)(i)?; + let (i, bootfilename) = take(128_usize)(i)?; + let (i, magic) = take(4_usize)(i)?; + Ok(( + i, + DHCPHeader { + opcode, + htype, + hlen, + hops, + txid, + seconds, + flags, + clientip: clientip.to_vec(), + yourip: yourip.to_vec(), + serverip: serverip.to_vec(), + giaddr: giaddr.to_vec(), + clienthw: clienthw[0..min(hlen as usize, 16)].to_vec(), + servername: servername.to_vec(), + bootfilename: bootfilename.to_vec(), + magic: magic.to_vec(), + }, + )) +} + +pub fn parse_clientid_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { + let (i, code) = be_u8(i)?; + let (i, len) = verify(be_u8, |&v| v > 1)(i)?; + let (i, _htype) = be_u8(i)?; + let (i, data) = take(len - 1)(i)?; + Ok(( + i, + DHCPOption { + code, + data: None, + option: DHCPOptionWrapper::ClientId(DHCPOptClientId { + htype: 1, + data: data.to_vec(), + }), + }, + )) +} + +pub fn parse_address_time_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { + let (i, code) = be_u8(i)?; + let (i, _len) = be_u8(i)?; + let (i, seconds) = be_u32(i)?; + Ok(( + i, + DHCPOption { + code, + data: None, + option: DHCPOptionWrapper::TimeValue(DHCPOptTimeValue { seconds }), + }, + )) +} + +pub fn parse_generic_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { + let (i, code) = be_u8(i)?; + let (i, len) = be_u8(i)?; + let (i, data) = take(len)(i)?; + Ok(( + i, + DHCPOption { + code, + data: None, + option: DHCPOptionWrapper::Generic(DHCPOptGeneric { + data: data.to_vec(), + }), + }, + )) +} + +// Parse a single DHCP option. When option 255 (END) is parsed, the remaining +// data will be consumed. +pub fn parse_option(i: &[u8]) -> IResult<&[u8], DHCPOption> { + let (_, opt) = be_u8(i)?; + match opt { + DHCP_OPT_END => { + // End of options case. We consume the rest of the data + // so the parser is not called again. But is there a + // better way to "break"? + let (data, code) = be_u8(i)?; + Ok(( + &[], + DHCPOption { + code, + data: Some(data.to_vec()), + option: DHCPOptionWrapper::End, + }, + )) + } + DHCP_OPT_CLIENT_ID => parse_clientid_option(i), + DHCP_OPT_ADDRESS_TIME => parse_address_time_option(i), + DHCP_OPT_RENEWAL_TIME => parse_address_time_option(i), + DHCP_OPT_REBINDING_TIME => parse_address_time_option(i), + _ => parse_generic_option(i), + } +} + +pub fn dhcp_parse(input: &[u8]) -> IResult<&[u8], DHCPMessage> { + match parse_header(input) { + Ok((rem, header)) => { + let mut options = Vec::new(); + let mut next = rem; + let malformed_options = false; + let mut truncated_options = false; + loop { + match parse_option(next) { + Ok((rem, option)) => { + let done = option.code == DHCP_OPT_END; + options.push(option); + next = rem; + if done { + break; + } + } + Err(_) => { + truncated_options = true; + break; + } + } + } + let message = DHCPMessage { + header, + options, + malformed_options, + truncated_options, + }; + return Ok((next, message)); + } + Err(err) => { + return Err(err); + } + } +} + +#[cfg(test)] +mod tests { + use crate::dhcp::dhcp::*; + use crate::dhcp::parser::*; + + #[test] + fn test_parse_discover() { + let pcap = include_bytes!("discover.pcap"); + let payload = &pcap[24 + 16 + 42..]; + + match dhcp_parse(payload) { + Ok((_rem, message)) => { + let header = message.header; + assert_eq!(header.opcode, BOOTP_REQUEST); + assert_eq!(header.htype, 1); + assert_eq!(header.hlen, 6); + assert_eq!(header.hops, 0); + assert_eq!(header.txid, 0x00003d1d); + assert_eq!(header.seconds, 0); + assert_eq!(header.flags, 0); + assert_eq!(header.clientip, &[0, 0, 0, 0]); + assert_eq!(header.yourip, &[0, 0, 0, 0]); + assert_eq!(header.serverip, &[0, 0, 0, 0]); + assert_eq!(header.giaddr, &[0, 0, 0, 0]); + assert_eq!( + &header.clienthw[..(header.hlen as usize)], + &[0x00, 0x0b, 0x82, 0x01, 0xfc, 0x42] + ); + assert!(header.servername.iter().all(|&x| x == 0)); + assert!(header.bootfilename.iter().all(|&x| x == 0)); + assert_eq!(header.magic, &[0x63, 0x82, 0x53, 0x63]); + + assert!(!message.malformed_options); + assert!(!message.truncated_options); + + assert_eq!(message.options.len(), 5); + assert_eq!(message.options[0].code, DHCP_OPT_TYPE); + assert_eq!(message.options[1].code, DHCP_OPT_CLIENT_ID); + assert_eq!(message.options[2].code, DHCP_OPT_REQUESTED_IP); + assert_eq!(message.options[3].code, DHCP_OPT_PARAMETER_LIST); + assert_eq!(message.options[4].code, DHCP_OPT_END); + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_parse_client_id_too_short() { + // Length field of 0. + let buf: &[u8] = &[ + 0x01, 0x00, // Length of 0. + 0x01, 0x01, // Junk data start here. + 0x02, 0x03, + ]; + let r = parse_clientid_option(buf); + assert!(r.is_err()); + + // Length field of 1. + let buf: &[u8] = &[ + 0x01, 0x01, // Length of 1. + 0x01, 0x41, + ]; + let r = parse_clientid_option(buf); + assert!(r.is_err()); + + // Length field of 2 -- OK. + let buf: &[u8] = &[ + 0x01, 0x02, // Length of 2. + 0x01, 0x41, + ]; + let r = parse_clientid_option(buf); + match r { + Ok((rem, _)) => { + assert_eq!(rem.len(), 0); + } + _ => { + panic!("failed"); + } + } + } +} diff --git a/rust/src/dhcp/request.pcap b/rust/src/dhcp/request.pcap Binary files differnew file mode 100644 index 0000000..5deb8b5 --- /dev/null +++ b/rust/src/dhcp/request.pcap diff --git a/rust/src/dns/detect.rs b/rust/src/dns/detect.rs new file mode 100644 index 0000000..268a409 --- /dev/null +++ b/rust/src/dns/detect.rs @@ -0,0 +1,199 @@ +/* Copyright (C) 2019 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::dns::DNSTransaction; +use crate::core::*; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; + +#[derive(Debug, PartialEq, Eq)] +pub struct DetectDnsOpcode { + negate: bool, + opcode: u8, +} + +/// Parse a DNS opcode argument returning the code and if it is to be +/// negated or not. +/// +/// For now only an indication that an error occurred is returned, not +/// the details of the error. +fn parse_opcode(opcode: &str) -> Result<DetectDnsOpcode, ()> { + let mut negated = false; + for (i, c) in opcode.chars().enumerate() { + match c { + ' ' | '\t' => { + continue; + } + '!' => { + negated = true; + } + _ => { + let code: u8 = opcode[i..].parse().map_err(|_| ())?; + return Ok(DetectDnsOpcode { + negate: negated, + opcode: code, + }); + } + } + } + Err(()) +} + +/// Perform the DNS opcode match. +/// +/// 1 will be returned on match, otherwise 0 will be returned. +#[no_mangle] +pub extern "C" fn rs_dns_opcode_match( + tx: &mut DNSTransaction, detect: &mut DetectDnsOpcode, flags: u8, +) -> u8 { + let header_flags = if flags & Direction::ToServer as u8 != 0 { + if let Some(request) = &tx.request { + request.header.flags + } else { + return 0; + } + } else if flags & Direction::ToClient as u8 != 0 { + if let Some(response) = &tx.response { + response.header.flags + } else { + return 0; + } + } else { + // Not to server or to client?? + return 0; + }; + + match_opcode(detect, header_flags).into() +} + +fn match_opcode(detect: &DetectDnsOpcode, flags: u16) -> bool { + let opcode = ((flags >> 11) & 0xf) as u8; + if detect.negate { + detect.opcode != opcode + } else { + detect.opcode == opcode + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_detect_dns_opcode_parse(carg: *const c_char) -> *mut c_void { + if carg.is_null() { + return std::ptr::null_mut(); + } + let arg = match CStr::from_ptr(carg).to_str() { + Ok(arg) => arg, + _ => { + return std::ptr::null_mut(); + } + }; + + match parse_opcode(arg) { + Ok(detect) => Box::into_raw(Box::new(detect)) as *mut _, + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_detect_opcode_free(ptr: *mut c_void) { + if !ptr.is_null() { + std::mem::drop(Box::from_raw(ptr as *mut DetectDnsOpcode)); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn parse_opcode_good() { + assert_eq!( + parse_opcode("1"), + Ok(DetectDnsOpcode { + negate: false, + opcode: 1 + }) + ); + assert_eq!( + parse_opcode("123"), + Ok(DetectDnsOpcode { + negate: false, + opcode: 123 + }) + ); + assert_eq!( + parse_opcode("!123"), + Ok(DetectDnsOpcode { + negate: true, + opcode: 123 + }) + ); + assert_eq!( + parse_opcode("!123"), + Ok(DetectDnsOpcode { + negate: true, + opcode: 123 + }) + ); + assert_eq!(parse_opcode(""), Err(())); + assert_eq!(parse_opcode("!"), Err(())); + assert_eq!(parse_opcode("! "), Err(())); + assert_eq!(parse_opcode("!asdf"), Err(())); + } + + #[test] + fn test_match_opcode() { + assert!( + match_opcode( + &DetectDnsOpcode { + negate: false, + opcode: 0, + }, + 0b0000_0000_0000_0000, + ) + ); + + assert!( + !match_opcode( + &DetectDnsOpcode { + negate: true, + opcode: 0, + }, + 0b0000_0000_0000_0000, + ) + ); + + assert!( + match_opcode( + &DetectDnsOpcode { + negate: false, + opcode: 4, + }, + 0b0010_0000_0000_0000, + ) + ); + + assert!( + !match_opcode( + &DetectDnsOpcode { + negate: true, + opcode: 4, + }, + 0b0010_0000_0000_0000, + ) + ); + } +} diff --git a/rust/src/dns/dns.rs b/rust/src/dns/dns.rs new file mode 100644 index 0000000..382c76a --- /dev/null +++ b/rust/src/dns/dns.rs @@ -0,0 +1,1526 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std; +use std::collections::HashMap; +use std::collections::VecDeque; +use std::ffi::CString; + +use crate::applayer::*; +use crate::core::{self, *}; +use crate::dns::parser; +use crate::frames::Frame; + +use nom7::number::streaming::be_u16; +use nom7::{Err, IResult}; + +/// DNS record types. +pub const DNS_RECORD_TYPE_A: u16 = 1; +pub const DNS_RECORD_TYPE_NS: u16 = 2; +pub const DNS_RECORD_TYPE_MD: u16 = 3; // Obsolete +pub const DNS_RECORD_TYPE_MF: u16 = 4; // Obsolete +pub const DNS_RECORD_TYPE_CNAME: u16 = 5; +pub const DNS_RECORD_TYPE_SOA: u16 = 6; +pub const DNS_RECORD_TYPE_MB: u16 = 7; // Experimental +pub const DNS_RECORD_TYPE_MG: u16 = 8; // Experimental +pub const DNS_RECORD_TYPE_MR: u16 = 9; // Experimental +pub const DNS_RECORD_TYPE_NULL: u16 = 10; // Experimental +pub const DNS_RECORD_TYPE_WKS: u16 = 11; +pub const DNS_RECORD_TYPE_PTR: u16 = 12; +pub const DNS_RECORD_TYPE_HINFO: u16 = 13; +pub const DNS_RECORD_TYPE_MINFO: u16 = 14; +pub const DNS_RECORD_TYPE_MX: u16 = 15; +pub const DNS_RECORD_TYPE_TXT: u16 = 16; +pub const DNS_RECORD_TYPE_RP: u16 = 17; +pub const DNS_RECORD_TYPE_AFSDB: u16 = 18; +pub const DNS_RECORD_TYPE_X25: u16 = 19; +pub const DNS_RECORD_TYPE_ISDN: u16 = 20; +pub const DNS_RECORD_TYPE_RT: u16 = 21; +pub const DNS_RECORD_TYPE_NSAP: u16 = 22; +pub const DNS_RECORD_TYPE_NSAPPTR: u16 = 23; +pub const DNS_RECORD_TYPE_SIG: u16 = 24; +pub const DNS_RECORD_TYPE_KEY: u16 = 25; +pub const DNS_RECORD_TYPE_PX: u16 = 26; +pub const DNS_RECORD_TYPE_GPOS: u16 = 27; +pub const DNS_RECORD_TYPE_AAAA: u16 = 28; +pub const DNS_RECORD_TYPE_LOC: u16 = 29; +pub const DNS_RECORD_TYPE_NXT: u16 = 30; // Obsolete +pub const DNS_RECORD_TYPE_SRV: u16 = 33; +pub const DNS_RECORD_TYPE_ATMA: u16 = 34; +pub const DNS_RECORD_TYPE_NAPTR: u16 = 35; +pub const DNS_RECORD_TYPE_KX: u16 = 36; +pub const DNS_RECORD_TYPE_CERT: u16 = 37; +pub const DNS_RECORD_TYPE_A6: u16 = 38; // Obsolete +pub const DNS_RECORD_TYPE_DNAME: u16 = 39; +pub const DNS_RECORD_TYPE_OPT: u16 = 41; +pub const DNS_RECORD_TYPE_APL: u16 = 42; +pub const DNS_RECORD_TYPE_DS: u16 = 43; +pub const DNS_RECORD_TYPE_SSHFP: u16 = 44; +pub const DNS_RECORD_TYPE_IPSECKEY: u16 = 45; +pub const DNS_RECORD_TYPE_RRSIG: u16 = 46; +pub const DNS_RECORD_TYPE_NSEC: u16 = 47; +pub const DNS_RECORD_TYPE_DNSKEY: u16 = 48; +pub const DNS_RECORD_TYPE_DHCID: u16 = 49; +pub const DNS_RECORD_TYPE_NSEC3: u16 = 50; +pub const DNS_RECORD_TYPE_NSEC3PARAM: u16 = 51; +pub const DNS_RECORD_TYPE_TLSA: u16 = 52; +pub const DNS_RECORD_TYPE_HIP: u16 = 55; +pub const DNS_RECORD_TYPE_CDS: u16 = 59; +pub const DNS_RECORD_TYPE_CDNSKEY: u16 = 60; +pub const DNS_RECORD_TYPE_HTTPS: u16 = 65; +pub const DNS_RECORD_TYPE_SPF: u16 = 99; // Obsolete +pub const DNS_RECORD_TYPE_TKEY: u16 = 249; +pub const DNS_RECORD_TYPE_TSIG: u16 = 250; +pub const DNS_RECORD_TYPE_MAILA: u16 = 254; // Obsolete +pub const DNS_RECORD_TYPE_ANY: u16 = 255; +pub const DNS_RECORD_TYPE_URI: u16 = 256; + +/// DNS error codes. +pub const DNS_RCODE_NOERROR: u16 = 0; +pub const DNS_RCODE_FORMERR: u16 = 1; +pub const DNS_RCODE_SERVFAIL: u16 = 2; +pub const DNS_RCODE_NXDOMAIN: u16 = 3; +pub const DNS_RCODE_NOTIMP: u16 = 4; +pub const DNS_RCODE_REFUSED: u16 = 5; +pub const DNS_RCODE_YXDOMAIN: u16 = 6; +pub const DNS_RCODE_YXRRSET: u16 = 7; +pub const DNS_RCODE_NXRRSET: u16 = 8; +pub const DNS_RCODE_NOTAUTH: u16 = 9; +pub const DNS_RCODE_NOTZONE: u16 = 10; +// Support for OPT RR from RFC6891 will be needed to +// parse RCODE values over 15 +pub const DNS_RCODE_BADVERS: u16 = 16; +//also pub const DNS_RCODE_BADSIG: u16 = 16; +pub const DNS_RCODE_BADKEY: u16 = 17; +pub const DNS_RCODE_BADTIME: u16 = 18; +pub const DNS_RCODE_BADMODE: u16 = 19; +pub const DNS_RCODE_BADNAME: u16 = 20; +pub const DNS_RCODE_BADALG: u16 = 21; +pub const DNS_RCODE_BADTRUNC: u16 = 22; + +static mut ALPROTO_DNS: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerFrameType)] +pub enum DnsFrameType { + /// DNS PDU frame. For UDP DNS this is the complete UDP payload, for TCP + /// this is the DNS payload not including the leading length field allowing + /// this frame to be used for UDP and TCP DNS. + Pdu, +} + +#[derive(Debug, PartialEq, Eq, AppLayerEvent)] +pub enum DNSEvent { + MalformedData, + NotRequest, + NotResponse, + ZFlagSet, + InvalidOpcode, +} + +#[derive(Debug, PartialEq, Eq)] +#[repr(C)] +pub struct DNSHeader { + pub tx_id: u16, + pub flags: u16, + pub questions: u16, + pub answer_rr: u16, + pub authority_rr: u16, + pub additional_rr: u16, +} + +#[derive(Debug)] +pub struct DNSQueryEntry { + pub name: Vec<u8>, + pub rrtype: u16, + pub rrclass: u16, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DNSRDataSOA { + /// Primary name server for this zone + pub mname: Vec<u8>, + /// Authority's mailbox + pub rname: Vec<u8>, + /// Serial version number + pub serial: u32, + /// Refresh interval (seconds) + pub refresh: u32, + /// Retry interval (seconds) + pub retry: u32, + /// Upper time limit until zone is no longer authoritative (seconds) + pub expire: u32, + /// Minimum ttl for records in this zone (seconds) + pub minimum: u32, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DNSRDataSSHFP { + /// Algorithm number + pub algo: u8, + /// Fingerprint type + pub fp_type: u8, + /// Fingerprint + pub fingerprint: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DNSRDataSRV { + /// Priority + pub priority: u16, + /// Weight + pub weight: u16, + /// Port + pub port: u16, + /// Target + pub target: Vec<u8>, +} + +/// Represents RData of various formats +#[derive(Debug, PartialEq, Eq)] +pub enum DNSRData { + // RData is an address + A(Vec<u8>), + AAAA(Vec<u8>), + // RData is a domain name + CNAME(Vec<u8>), + PTR(Vec<u8>), + MX(Vec<u8>), + NS(Vec<u8>), + // RData is text + TXT(Vec<u8>), + NULL(Vec<u8>), + // RData has several fields + SOA(DNSRDataSOA), + SRV(DNSRDataSRV), + SSHFP(DNSRDataSSHFP), + // RData for remaining types is sometimes ignored + Unknown(Vec<u8>), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DNSAnswerEntry { + pub name: Vec<u8>, + pub rrtype: u16, + pub rrclass: u16, + pub ttl: u32, + pub data: DNSRData, +} + +#[derive(Debug)] +pub struct DNSRequest { + pub header: DNSHeader, + pub queries: Vec<DNSQueryEntry>, +} + +#[derive(Debug)] +pub struct DNSResponse { + pub header: DNSHeader, + pub queries: Vec<DNSQueryEntry>, + pub answers: Vec<DNSAnswerEntry>, + pub authorities: Vec<DNSAnswerEntry>, +} + +#[derive(Debug, Default)] +pub struct DNSTransaction { + pub id: u64, + pub request: Option<DNSRequest>, + pub response: Option<DNSResponse>, + pub tx_data: AppLayerTxData, +} + +impl Transaction for DNSTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl DNSTransaction { + pub fn new(direction: Direction) -> Self { + Self { + tx_data: AppLayerTxData::for_direction(direction), + ..Default::default() + } + } + + /// Get the DNS transactions ID (not the internal tracking ID). + pub fn tx_id(&self) -> u16 { + if let Some(request) = &self.request { + return request.header.tx_id; + } + if let Some(response) = &self.response { + return response.header.tx_id; + } + + // Shouldn't happen. + return 0; + } + + /// Get the reply code of the transaction. Note that this will + /// also return 0 if there is no reply. + pub fn rcode(&self) -> u16 { + if let Some(response) = &self.response { + return response.header.flags & 0x000f; + } + return 0; + } +} + +struct ConfigTracker { + map: HashMap<u16, AppLayerTxConfig>, + queue: VecDeque<u16>, +} + +impl ConfigTracker { + fn new() -> ConfigTracker { + ConfigTracker { + map: HashMap::new(), + queue: VecDeque::new(), + } + } + + fn add(&mut self, id: u16, config: AppLayerTxConfig) { + // If at size limit, remove the oldest entry. + if self.queue.len() > 499 { + if let Some(id) = self.queue.pop_front() { + self.map.remove(&id); + } + } + + self.map.insert(id, config); + self.queue.push_back(id); + } + + fn remove(&mut self, id: &u16) -> Option<AppLayerTxConfig> { + self.map.remove(id) + } +} + +#[derive(Default)] +pub struct DNSState { + state_data: AppLayerStateData, + + // Internal transaction ID. + pub tx_id: u64, + + // Transactions. + pub transactions: VecDeque<DNSTransaction>, + + config: Option<ConfigTracker>, + + gap: bool, +} + +impl State<DNSTransaction> for DNSState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&DNSTransaction> { + self.transactions.get(index) + } +} + +impl DNSState { + pub fn new() -> Self { + Default::default() + } + + pub fn new_tx(&mut self, direction: Direction) -> DNSTransaction { + let mut tx = DNSTransaction::new(direction); + self.tx_id += 1; + tx.id = self.tx_id; + 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 { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&DNSTransaction> { + SCLogDebug!("get_tx: tx_id={}", tx_id); + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + SCLogDebug!("Found DNS TX with ID {}", tx_id); + return Some(tx); + } + } + SCLogDebug!("Failed to find DNS TX with ID {}", tx_id); + return None; + } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: DNSEvent) { + let len = self.transactions.len(); + if len == 0 { + return; + } + + let tx = &mut self.transactions[len - 1]; + tx.tx_data.set_event(event as u8); + } + + fn validate_header<'a>(&self, input: &'a [u8]) -> Option<(&'a [u8], DNSHeader)> { + if let Ok((body, header)) = parser::dns_parse_header(input) { + if probe_header_validity(&header, input.len()).0 { + return Some((body, header)); + } + } + None + } + + fn parse_request(&mut self, input: &[u8], is_tcp: bool) -> bool { + let (body, header) = if let Some((body, header)) = self.validate_header(input) { + (body, header) + } else { + return !is_tcp; + }; + + match parser::dns_parse_request_body(body, input, header) { + Ok((_, request)) => { + if request.header.flags & 0x8000 != 0 { + SCLogDebug!("DNS message is not a request"); + self.set_event(DNSEvent::NotRequest); + return false; + } + + let z_flag = request.header.flags & 0x0040 != 0; + let opcode = ((request.header.flags >> 11) & 0xf) as u8; + + let mut tx = self.new_tx(Direction::ToServer); + tx.request = Some(request); + self.transactions.push_back(tx); + + if z_flag { + SCLogDebug!("Z-flag set on DNS response"); + self.set_event(DNSEvent::ZFlagSet); + } + + if opcode >= 7 { + self.set_event(DNSEvent::InvalidOpcode); + } + + return true; + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS request"); + self.set_event(DNSEvent::MalformedData); + return false; + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS request"); + self.set_event(DNSEvent::MalformedData); + return false; + } + } + } + + fn parse_request_udp(&mut self, flow: *const core::Flow, stream_slice: StreamSlice) -> bool { + let input = stream_slice.as_slice(); + let _pdu = Frame::new( + flow, + &stream_slice, + input, + input.len() as i64, + DnsFrameType::Pdu as u8, + ); + self.parse_request(input, false) + } + + fn parse_response_udp(&mut self, flow: *const core::Flow, stream_slice: StreamSlice) -> bool { + let input = stream_slice.as_slice(); + let _pdu = Frame::new( + flow, + &stream_slice, + input, + input.len() as i64, + DnsFrameType::Pdu as u8, + ); + self.parse_response(input, false) + } + + pub fn parse_response(&mut self, input: &[u8], is_tcp: bool) -> bool { + let (body, header) = if let Some((body, header)) = self.validate_header(input) { + (body, header) + } else { + return !is_tcp; + }; + + match parser::dns_parse_response_body(body, input, header) { + Ok((_, response)) => { + SCLogDebug!("Response header flags: {}", response.header.flags); + + if response.header.flags & 0x8000 == 0 { + SCLogDebug!("DNS message is not a response"); + self.set_event(DNSEvent::NotResponse); + } + + let z_flag = response.header.flags & 0x0040 != 0; + let opcode = ((response.header.flags >> 11) & 0xf) as u8; + + let mut tx = self.new_tx(Direction::ToClient); + if let Some(ref mut config) = &mut self.config { + if let Some(config) = config.remove(&response.header.tx_id) { + tx.tx_data.config = config; + } + } + tx.response = Some(response); + self.transactions.push_back(tx); + + if z_flag { + SCLogDebug!("Z-flag set on DNS response"); + self.set_event(DNSEvent::ZFlagSet); + } + + if opcode >= 7 { + self.set_event(DNSEvent::InvalidOpcode); + } + + return true; + } + Err(Err::Incomplete(_)) => { + // Insufficient data. + SCLogDebug!("Insufficient data while parsing DNS response"); + self.set_event(DNSEvent::MalformedData); + return false; + } + Err(_) => { + // Error, probably malformed data. + SCLogDebug!("An error occurred while parsing DNS response"); + self.set_event(DNSEvent::MalformedData); + return false; + } + } + } + + /// TCP variation of response request parser to handle the length + /// prefix. + /// + /// Returns the number of messages parsed. + pub fn parse_request_tcp( + &mut self, flow: *const core::Flow, stream_slice: StreamSlice, + ) -> AppLayerResult { + let input = stream_slice.as_slice(); + if self.gap { + let (is_dns, _, is_incomplete) = probe_tcp(input); + if is_dns || is_incomplete { + self.gap = false; + } else { + AppLayerResult::ok(); + } + } + + let mut cur_i = input; + let mut consumed = 0; + while !cur_i.is_empty() { + if cur_i.len() == 1 { + return AppLayerResult::incomplete(consumed as u32, 2_u32); + } + let size = match be_u16(cur_i) as IResult<&[u8], u16> { + Ok((_, len)) => len, + _ => 0, + } as usize; + SCLogDebug!( + "[request] Have {} bytes, need {} to parse", + cur_i.len(), + size + 2 + ); + if size > 0 && cur_i.len() >= size + 2 { + let msg = &cur_i[2..(size + 2)]; + let _pdu = Frame::new( + flow, + &stream_slice, + msg, + msg.len() as i64, + DnsFrameType::Pdu as u8, + ); + if self.parse_request(msg, true) { + cur_i = &cur_i[(size + 2)..]; + consumed += size + 2; + } else { + return AppLayerResult::err(); + } + } else if size == 0 { + cur_i = &cur_i[2..]; + consumed += 2; + } else { + SCLogDebug!( + "[request]Not enough DNS traffic to parse. Returning {}/{}", + consumed as u32, + (size + 2) as u32 + ); + return AppLayerResult::incomplete(consumed as u32, (size + 2) as u32); + } + } + AppLayerResult::ok() + } + + /// TCP variation of the response parser to handle the length + /// prefix. + /// + /// Returns the number of messages parsed. + pub fn parse_response_tcp( + &mut self, flow: *const core::Flow, stream_slice: StreamSlice, + ) -> AppLayerResult { + let input = stream_slice.as_slice(); + if self.gap { + let (is_dns, _, is_incomplete) = probe_tcp(input); + if is_dns || is_incomplete { + self.gap = false; + } else { + return AppLayerResult::ok(); + } + } + + let mut cur_i = input; + let mut consumed = 0; + while !cur_i.is_empty() { + if cur_i.len() == 1 { + return AppLayerResult::incomplete(consumed as u32, 2_u32); + } + let size = match be_u16(cur_i) as IResult<&[u8], u16> { + Ok((_, len)) => len, + _ => 0, + } as usize; + SCLogDebug!( + "[response] Have {} bytes, need {} to parse", + cur_i.len(), + size + 2 + ); + if size > 0 && cur_i.len() >= size + 2 { + let msg = &cur_i[2..(size + 2)]; + let _pdu = Frame::new( + flow, + &stream_slice, + msg, + msg.len() as i64, + DnsFrameType::Pdu as u8, + ); + if self.parse_response(msg, true) { + cur_i = &cur_i[(size + 2)..]; + consumed += size + 2; + } else { + return AppLayerResult::err(); + } + } else if size == 0 { + cur_i = &cur_i[2..]; + consumed += 2; + } else { + SCLogDebug!( + "[response]Not enough DNS traffic to parse. Returning {}/{}", + consumed as u32, + (cur_i.len() - consumed) as u32 + ); + return AppLayerResult::incomplete(consumed as u32, (size + 2) as u32); + } + } + AppLayerResult::ok() + } + + /// A gap has been seen in the request direction. Set the gap flag. + pub fn request_gap(&mut self, gap: u32) { + if gap > 0 { + self.gap = true; + } + } + + /// A gap has been seen in the response direction. Set the gap + /// flag. + pub fn response_gap(&mut self, gap: u32) { + if gap > 0 { + self.gap = true; + } + } +} + +const DNS_HEADER_SIZE: usize = 12; + +fn probe_header_validity(header: &DNSHeader, rlen: usize) -> (bool, bool, bool) { + let min_msg_size = 2 + * (header.additional_rr as usize + + header.answer_rr as usize + + header.authority_rr as usize + + header.questions as usize) + + DNS_HEADER_SIZE; + + if min_msg_size > rlen { + // Not enough data for records defined in the header, or + // impossibly large. + return (false, false, false); + } + + let is_request = header.flags & 0x8000 == 0; + return (true, is_request, false); +} + +/// Probe input to see if it looks like DNS. +/// +/// Returns a tuple of booleans: (is_dns, is_request, incomplete) +fn probe(input: &[u8], dlen: usize) -> (bool, bool, bool) { + // Trim input to dlen if larger. + let input = if input.len() <= dlen { + input + } else { + &input[..dlen] + }; + + // If input is less than dlen then we know we don't have enough data to + // parse a complete message, so perform header validation only. + if input.len() < dlen { + if let Ok((_, header)) = parser::dns_parse_header(input) { + return probe_header_validity(&header, dlen); + } else { + return (false, false, false); + } + } + + match parser::dns_parse_request(input) { + Ok((_, request)) => { + return probe_header_validity(&request.header, dlen); + } + Err(Err::Incomplete(_)) => match parser::dns_parse_header(input) { + Ok((_, header)) => { + return probe_header_validity(&header, dlen); + } + Err(Err::Incomplete(_)) => (false, false, true), + Err(_) => (false, false, false), + }, + Err(_) => (false, false, false), + } +} + +/// Probe TCP input to see if it looks like DNS. +pub fn probe_tcp(input: &[u8]) -> (bool, bool, bool) { + match be_u16(input) as IResult<&[u8], u16> { + Ok((rem, dlen)) => { + return probe(rem, dlen as usize); + } + Err(Err::Incomplete(_)) => { + return (false, false, true); + } + _ => {} + } + return (false, false, false); +} + +/// Returns *mut DNSState +#[no_mangle] +pub extern "C" fn rs_dns_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = DNSState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +/// Returns *mut DNSState +#[no_mangle] +pub extern "C" fn rs_dns_state_tcp_new() -> *mut std::os::raw::c_void { + let state = DNSState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut DNSState as void pointer +#[no_mangle] +pub extern "C" fn rs_dns_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(unsafe { Box::from_raw(state as *mut DNSState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, DNSState); + state.free_tx(tx_id); +} + +/// C binding parse a DNS request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_dns_parse_request( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DNSState); + state.parse_request_udp(flow, stream_slice); + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_parse_response( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DNSState); + state.parse_response_udp(flow, stream_slice); + AppLayerResult::ok() +} + +/// C binding parse a DNS request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_dns_parse_request_tcp( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DNSState); + if stream_slice.is_gap() { + state.request_gap(stream_slice.gap_size()); + } else if !stream_slice.is_empty() { + return state.parse_request_tcp(flow, stream_slice); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_parse_response_tcp( + flow: *const core::Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, DNSState); + if stream_slice.is_gap() { + state.response_gap(stream_slice.gap_size()); + } else if !stream_slice.is_empty() { + return state.parse_response_tcp(flow, stream_slice); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_alstate_progress( + _tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + // This is a stateless parser, just the existence of a transaction + // means its complete. + SCLogDebug!("rs_dns_tx_get_alstate_progress"); + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, DNSState); + SCLogDebug!("rs_dns_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, DNSState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_is_request(tx: &mut DNSTransaction) -> bool { + tx.request.is_some() +} + +#[no_mangle] +pub extern "C" fn rs_dns_tx_is_response(tx: &mut DNSTransaction) -> bool { + tx.response.is_some() +} + +pub unsafe extern "C" fn rs_dns_state_get_tx_data( + tx: *mut std::os::raw::c_void, +) -> *mut AppLayerTxData { + let tx = cast_pointer!(tx, DNSTransaction); + return &mut tx.tx_data; +} + +export_state_data_get!(rs_dns_get_state_data, DNSState); + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_tx_get_query_name( + tx: &mut DNSTransaction, i: u32, buf: *mut *const u8, len: *mut u32, +) -> u8 { + if let Some(request) = &tx.request { + if (i as usize) < request.queries.len() { + let query = &request.queries[i as usize]; + if !query.name.is_empty() { + *len = query.name.len() as u32; + *buf = query.name.as_ptr(); + return 1; + } + } + } + return 0; +} + +/// Get the DNS transaction ID of a transaction. +// +/// extern uint16_t rs_dns_tx_get_tx_id(RSDNSTransaction *); +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_tx_id(tx: &mut DNSTransaction) -> u16 { + return tx.tx_id(); +} + +/// Get the DNS response flags for a transaction. +/// +/// extern uint16_t rs_dns_tx_get_response_flags(RSDNSTransaction *); +#[no_mangle] +pub extern "C" fn rs_dns_tx_get_response_flags(tx: &mut DNSTransaction) -> u16 { + return tx.rcode(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_tx_get_query_rrtype( + tx: &mut DNSTransaction, i: u16, rrtype: *mut u16, +) -> u8 { + if let Some(request) = &tx.request { + if (i as usize) < request.queries.len() { + let query = &request.queries[i as usize]; + if !query.name.is_empty() { + *rrtype = query.rrtype; + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_probe( + _flow: *const core::Flow, _dir: u8, input: *const u8, len: u32, rdir: *mut u8, +) -> AppProto { + if len == 0 || len < std::mem::size_of::<DNSHeader>() as u32 { + return core::ALPROTO_UNKNOWN; + } + let slice: &[u8] = std::slice::from_raw_parts(input as *mut u8, len as usize); + let (is_dns, is_request, _) = probe(slice, slice.len()); + if is_dns { + let dir = if is_request { + Direction::ToServer + } else { + Direction::ToClient + }; + *rdir = dir as u8; + return ALPROTO_DNS; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_probe_tcp( + _flow: *const core::Flow, direction: u8, input: *const u8, len: u32, rdir: *mut u8, +) -> AppProto { + if len == 0 || len < std::mem::size_of::<DNSHeader>() as u32 + 2 { + return core::ALPROTO_UNKNOWN; + } + let slice: &[u8] = std::slice::from_raw_parts(input as *mut u8, len as usize); + //is_incomplete is checked by caller + let (is_dns, is_request, _) = probe_tcp(slice); + if is_dns { + let dir = if is_request { + Direction::ToServer + } else { + Direction::ToClient + }; + if (direction & DIR_BOTH) != dir.into() { + *rdir = dir as u8; + } + return ALPROTO_DNS; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_apply_tx_config( + _state: *mut std::os::raw::c_void, _tx: *mut std::os::raw::c_void, _mode: std::os::raw::c_int, + config: AppLayerTxConfig, +) { + let tx = cast_pointer!(_tx, DNSTransaction); + let state = cast_pointer!(_state, DNSState); + if let Some(request) = &tx.request { + if state.config.is_none() { + state.config = Some(ConfigTracker::new()); + } + if let Some(ref mut tracker) = &mut state.config { + tracker.add(request.header.tx_id, config); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_udp_register_parser() { + let default_port = std::ffi::CString::new("[53]").unwrap(); + let parser = RustParser { + name: b"dns\0".as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_UDP, + probe_ts: Some(rs_dns_probe), + probe_tc: Some(rs_dns_probe), + min_depth: 0, + max_depth: std::mem::size_of::<DNSHeader>() as u16, + state_new: rs_dns_state_new, + state_free: rs_dns_state_free, + tx_free: rs_dns_state_tx_free, + parse_ts: rs_dns_parse_request, + parse_tc: rs_dns_parse_response, + get_tx_count: rs_dns_state_get_tx_count, + get_tx: rs_dns_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_dns_tx_get_alstate_progress, + get_eventinfo: Some(DNSEvent::get_event_info), + get_eventinfo_byid: Some(DNSEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(crate::applayer::state_get_tx_iterator::<DNSState, DNSTransaction>), + get_tx_data: rs_dns_state_get_tx_data, + get_state_data: rs_dns_get_state_data, + apply_tx_config: Some(rs_dns_apply_tx_config), + flags: 0, + truncate: None, + get_frame_id_by_name: Some(DnsFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(DnsFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DNS = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_dns_tcp_register_parser() { + let default_port = std::ffi::CString::new("53").unwrap(); + let parser = RustParser { + name: b"dns\0".as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_dns_probe_tcp), + probe_tc: Some(rs_dns_probe_tcp), + min_depth: 0, + max_depth: std::mem::size_of::<DNSHeader>() as u16 + 2, + state_new: rs_dns_state_new, + state_free: rs_dns_state_free, + tx_free: rs_dns_state_tx_free, + parse_ts: rs_dns_parse_request_tcp, + parse_tc: rs_dns_parse_response_tcp, + get_tx_count: rs_dns_state_get_tx_count, + get_tx: rs_dns_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_dns_tx_get_alstate_progress, + get_eventinfo: Some(DNSEvent::get_event_info), + get_eventinfo_byid: Some(DNSEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(crate::applayer::state_get_tx_iterator::<DNSState, DNSTransaction>), + get_tx_data: rs_dns_state_get_tx_data, + get_state_data: rs_dns_get_state_data, + apply_tx_config: Some(rs_dns_apply_tx_config), + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: Some(DnsFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(DnsFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_DNS = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_dns_parse_request_tcp_valid() { + // A UDP DNS request with the DNS payload starting at byte 42. + // From pcap: https://github.com/jasonish/suricata-verify/blob/7cc0e1bd0a5249b52e6e87d82d57c0b6aaf75fce/dns-udp-dig-a-www-suricata-ids-org/dig-a-www.suricata-ids.org.pcap + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x15, 0x17, 0x0d, 0x06, 0xf7, 0xd8, 0xcb, /* ........ */ + 0x8a, 0xed, 0xa1, 0x46, 0x08, 0x00, 0x45, 0x00, /* ...F..E. */ + 0x00, 0x4d, 0x23, 0x11, 0x00, 0x00, 0x40, 0x11, /* .M#...@. */ + 0x41, 0x64, 0x0a, 0x10, 0x01, 0x0b, 0x0a, 0x10, /* Ad...... */ + 0x01, 0x01, 0xa3, 0x4d, 0x00, 0x35, 0x00, 0x39, /* ...M.5.9 */ + 0xb2, 0xb3, 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */ + 0x00, 0x00, 0x00 /* ... */ + ]; + + // The DNS payload starts at offset 42. + let dns_payload = &buf[42..]; + + // Make a TCP DNS request payload. + let mut request = Vec::new(); + request.push(((dns_payload.len() as u16) >> 8) as u8); + request.push(((dns_payload.len() as u16) & 0xff) as u8); + request.extend(dns_payload); + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse_request_tcp( + std::ptr::null(), + StreamSlice::from_slice(&request, STREAM_TOSERVER, 0) + ) + ); + } + + #[test] + fn test_dns_parse_request_tcp_short_payload() { + // A UDP DNS request with the DNS payload starting at byte 42. + // From pcap: https://github.com/jasonish/suricata-verify/blob/7cc0e1bd0a5249b52e6e87d82d57c0b6aaf75fce/dns-udp-dig-a-www-suricata-ids-org/dig-a-www.suricata-ids.org.pcap + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x15, 0x17, 0x0d, 0x06, 0xf7, 0xd8, 0xcb, /* ........ */ + 0x8a, 0xed, 0xa1, 0x46, 0x08, 0x00, 0x45, 0x00, /* ...F..E. */ + 0x00, 0x4d, 0x23, 0x11, 0x00, 0x00, 0x40, 0x11, /* .M#...@. */ + 0x41, 0x64, 0x0a, 0x10, 0x01, 0x0b, 0x0a, 0x10, /* Ad...... */ + 0x01, 0x01, 0xa3, 0x4d, 0x00, 0x35, 0x00, 0x39, /* ...M.5.9 */ + 0xb2, 0xb3, 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */ + 0x00, 0x00, 0x00 /* ... */ + ]; + + // The DNS payload starts at offset 42. + let dns_payload = &buf[42..]; + + // Make a TCP DNS request payload but with the length 1 larger + // than the available data. + let mut request = Vec::new(); + request.push(((dns_payload.len() as u16) >> 8) as u8); + request.push(((dns_payload.len() as u16) & 0xff) as u8 + 1); + request.extend(dns_payload); + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::incomplete(0, 52), + state.parse_request_tcp( + std::ptr::null(), + StreamSlice::from_slice(&request, STREAM_TOSERVER, 0) + ) + ); + } + + #[test] + fn test_dns_parse_response_tcp_valid() { + // A UDP DNS response with the DNS payload starting at byte 42. + // From pcap: https://github.com/jasonish/suricata-verify/blob/7cc0e1bd0a5249b52e6e87d82d57c0b6aaf75fce/dns-udp-dig-a-www-suricata-ids-org/dig-a-www.suricata-ids.org.pcap + #[rustfmt::skip] + let buf: &[u8] = &[ + 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* .....F.. */ + 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* ......E. */ + 0x00, 0x80, 0x65, 0x4e, 0x40, 0x00, 0x40, 0x11, /* ..eN@.@. */ + 0xbe, 0xf3, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10, /* ........ */ + 0x01, 0x0b, 0x00, 0x35, 0xa3, 0x4d, 0x00, 0x6c, /* ...5.M.l */ + 0x8d, 0x8c, 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */ + 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */ + 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */ + 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19 /* ....N. */ + ]; + + // The DNS payload starts at offset 42. + let dns_payload = &buf[42..]; + + // Make a TCP DNS response payload. + let mut request = Vec::new(); + request.push(((dns_payload.len() as u16) >> 8) as u8); + request.push(((dns_payload.len() as u16) & 0xff) as u8); + request.extend(dns_payload); + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse_response_tcp( + std::ptr::null(), + StreamSlice::from_slice(&request, STREAM_TOCLIENT, 0) + ) + ); + } + + // Test that a TCP DNS payload won't be parsed if there is not + // enough data. + #[test] + fn test_dns_parse_response_tcp_short_payload() { + // A UDP DNS response with the DNS payload starting at byte 42. + // From pcap: https://github.com/jasonish/suricata-verify/blob/7cc0e1bd0a5249b52e6e87d82d57c0b6aaf75fce/dns-udp-dig-a-www-suricata-ids-org/dig-a-www.suricata-ids.org.pcap + #[rustfmt::skip] + let buf: &[u8] = &[ + 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* .....F.. */ + 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* ......E. */ + 0x00, 0x80, 0x65, 0x4e, 0x40, 0x00, 0x40, 0x11, /* ..eN@.@. */ + 0xbe, 0xf3, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10, /* ........ */ + 0x01, 0x0b, 0x00, 0x35, 0xa3, 0x4d, 0x00, 0x6c, /* ...5.M.l */ + 0x8d, 0x8c, 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */ + 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */ + 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */ + 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19 /* ....N. */ + ]; + + // The DNS payload starts at offset 42. + let dns_payload = &buf[42..]; + + // Make a TCP DNS response payload, but make the length 1 byte + // larger than the actual size. + let mut request = Vec::new(); + request.push(((dns_payload.len() as u16) >> 8) as u8); + request.push((((dns_payload.len() as u16) & 0xff) + 1) as u8); + request.extend(dns_payload); + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::incomplete(0, 103), + state.parse_response_tcp( + std::ptr::null(), + StreamSlice::from_slice(&request, STREAM_TOCLIENT, 0) + ) + ); + } + + // Port of the C RustDNSUDPParserTest02 unit test. + #[test] + fn test_dns_udp_parser_test_01() { + /* query: abcdefghijk.com + * TTL: 86400 + * serial 20130422 refresh 28800 retry 7200 exp 604800 min ttl 86400 + * ns, hostmaster */ + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x3c, 0x85, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x0b, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x0f, 0x00, + 0x01, 0x00, 0x00, 0x06, 0x00, 0x01, 0x00, 0x01, + 0x51, 0x80, 0x00, 0x25, 0x02, 0x6e, 0x73, 0x00, + 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x6d, 0x61, 0x73, + 0x74, 0x65, 0x72, 0xc0, 0x2f, 0x01, 0x33, 0x2a, + 0x76, 0x00, 0x00, 0x70, 0x80, 0x00, 0x00, 0x1c, + 0x20, 0x00, 0x09, 0x3a, 0x80, 0x00, 0x01, 0x51, + 0x80, + ]; + let mut state = DNSState::new(); + assert!(state.parse_response(buf, false)); + } + + // Port of the C RustDNSUDPParserTest02 unit test. + #[test] + fn test_dns_udp_parser_test_02() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x6D,0x08,0x84,0x80,0x00,0x01,0x00,0x08,0x00,0x00,0x00,0x01,0x03,0x57,0x57,0x57, + 0x04,0x54,0x54,0x54,0x54,0x03,0x56,0x56,0x56,0x03,0x63,0x6F,0x6D,0x02,0x79,0x79, + 0x00,0x00,0x01,0x00,0x01,0xC0,0x0C,0x00,0x05,0x00,0x01,0x00,0x00,0x0E,0x10,0x00, + 0x02,0xC0,0x0C,0xC0,0x31,0x00,0x05,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x02,0xC0, + 0x31,0xC0,0x3F,0x00,0x05,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x02,0xC0,0x3F,0xC0, + 0x4D,0x00,0x05,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x02,0xC0,0x4D,0xC0,0x5B,0x00, + 0x05,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x02,0xC0,0x5B,0xC0,0x69,0x00,0x05,0x00, + 0x01,0x00,0x00,0x0E,0x10,0x00,0x02,0xC0,0x69,0xC0,0x77,0x00,0x05,0x00,0x01,0x00, + 0x00,0x0E,0x10,0x00,0x02,0xC0,0x77,0xC0,0x85,0x00,0x05,0x00,0x01,0x00,0x00,0x0E, + 0x10,0x00,0x02,0xC0,0x85,0x00,0x00,0x29,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + ]; + let mut state = DNSState::new(); + assert!(state.parse_response(buf, false)); + } + + // Port of the C RustDNSUDPParserTest03 unit test. + #[test] + fn test_dns_udp_parser_test_03() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x6F,0xB4,0x84,0x80,0x00,0x01,0x00,0x02,0x00,0x02,0x00,0x03,0x03,0x57,0x57,0x77, + 0x0B,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x56,0x03,0x55,0x55,0x55, + 0x02,0x79,0x79,0x00,0x00,0x01,0x00,0x01,0xC0,0x0C,0x00,0x05,0x00,0x01,0x00,0x00, + 0x0E,0x10,0x00,0x02,0xC0,0x10,0xC0,0x34,0x00,0x01,0x00,0x01,0x00,0x00,0x0E,0x10, + 0x00,0x04,0xC3,0xEA,0x04,0x19,0xC0,0x34,0x00,0x02,0x00,0x01,0x00,0x00,0x0E,0x10, + 0x00,0x0A,0x03,0x6E,0x73,0x31,0x03,0x61,0x67,0x62,0xC0,0x20,0xC0,0x46,0x00,0x02, + 0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x06,0x03,0x6E,0x73,0x32,0xC0,0x56,0xC0,0x52, + 0x00,0x01,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x04,0xC3,0xEA,0x04,0x0A,0xC0,0x68, + 0x00,0x01,0x00,0x01,0x00,0x00,0x0E,0x10,0x00,0x04,0xC3,0xEA,0x05,0x14,0x00,0x00, + 0x29,0x05,0x00,0x00,0x00,0x00,0x00,0x00,0x00 + ]; + let mut state = DNSState::new(); + assert!(state.parse_response(buf, false)); + } + + // Port of the C RustDNSUDPParserTest04 unit test. + // + // Test the TXT records in an answer. + #[test] + fn test_dns_udp_parser_test_04() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0xc2,0x2f,0x81,0x80,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x0a,0x41,0x41,0x41, + 0x41,0x41,0x4f,0x31,0x6b,0x51,0x41,0x05,0x3d,0x61,0x75,0x74,0x68,0x03,0x73,0x72, + 0x76,0x06,0x74,0x75,0x6e,0x6e,0x65,0x6c,0x03,0x63,0x6f,0x6d,0x00,0x00,0x10,0x00, + 0x01, + /* answer record start */ + 0xc0,0x0c,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x22, + /* txt record starts: */ + 0x20, /* <txt len 32 */ 0x41,0x68,0x76,0x4d,0x41,0x41,0x4f,0x31,0x6b,0x41,0x46, + 0x45,0x35,0x54,0x45,0x39,0x51,0x54,0x6a,0x46,0x46,0x4e,0x30,0x39,0x52,0x4e,0x31, + 0x6c,0x59,0x53,0x44,0x6b,0x00, /* <txt len 0 */ 0xc0,0x1d,0x00,0x02,0x00,0x01, + 0x00,0x09,0x3a,0x80,0x00,0x09,0x06,0x69,0x6f,0x64,0x69,0x6e,0x65,0xc0,0x21,0xc0, + 0x6b,0x00,0x01,0x00,0x01,0x00,0x09,0x3a,0x80,0x00,0x04,0x0a,0x1e,0x1c,0x5f + ]; + let mut state = DNSState::new(); + assert!(state.parse_response(buf, false)); + } + + // Port of the C RustDNSUDPParserTest05 unit test. + // + // Test TXT records in answer with a bad length. + #[test] + fn test_dns_udp_parser_test_05() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0xc2,0x2f,0x81,0x80,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x0a,0x41,0x41,0x41, + 0x41,0x41,0x4f,0x31,0x6b,0x51,0x41,0x05,0x3d,0x61,0x75,0x74,0x68,0x03,0x73,0x72, + 0x76,0x06,0x74,0x75,0x6e,0x6e,0x65,0x6c,0x03,0x63,0x6f,0x6d,0x00,0x00,0x10,0x00, + 0x01, + /* answer record start */ + 0xc0,0x0c,0x00,0x10,0x00,0x01,0x00,0x00,0x00,0x03,0x00,0x22, + /* txt record starts: */ + 0x40, /* <txt len 64 */ 0x41,0x68,0x76,0x4d,0x41,0x41,0x4f,0x31,0x6b,0x41,0x46, + 0x45,0x35,0x54,0x45,0x39,0x51,0x54,0x6a,0x46,0x46,0x4e,0x30,0x39,0x52,0x4e,0x31, + 0x6c,0x59,0x53,0x44,0x6b,0x00, /* <txt len 0 */ 0xc0,0x1d,0x00,0x02,0x00,0x01, + 0x00,0x09,0x3a,0x80,0x00,0x09,0x06,0x69,0x6f,0x64,0x69,0x6e,0x65,0xc0,0x21,0xc0, + 0x6b,0x00,0x01,0x00,0x01,0x00,0x09,0x3a,0x80,0x00,0x04,0x0a,0x1e,0x1c,0x5f + ]; + let mut state = DNSState::new(); + assert!(!state.parse_response(buf, false)); + } + + // Port of the C RustDNSTCPParserTestMultiRecord unit test. + #[test] + fn test_dns_tcp_parser_multi_record() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x1e, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x30, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x31, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x02, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x32, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x03, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x33, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x04, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x34, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x05, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x35, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x06, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x36, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x07, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x37, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x08, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x38, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1e, 0x00, 0x09, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x39, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1f, 0x00, 0x0a, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, + 0x30, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x1f, 0x00, 0x0b, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x31, 0x31, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x1f, 0x00, 0x0c, 0x01, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x31, 0x32, 0x06, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x0d, 0x01, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x31, 0x33, 0x06, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, + 0x00, 0x01, 0x00, 0x01, 0x00, 0x1f, 0x00, 0x0e, + 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x02, 0x31, 0x34, 0x06, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1f, 0x00, + 0x0f, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x31, 0x35, 0x06, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x1f, + 0x00, 0x10, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, 0x36, 0x06, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, + 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, + 0x1f, 0x00, 0x11, 0x01, 0x00, 0x00, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, 0x37, + 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, + 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x1f, 0x00, 0x12, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x31, + 0x38, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, + 0x01, 0x00, 0x1f, 0x00, 0x13, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x31, 0x39, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, + 0x00, 0x01 + ]; + + // A NULL flow. + let flow = std::ptr::null(); + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse_request_tcp(flow, StreamSlice::from_slice(buf, STREAM_TOSERVER, 0)) + ); + } + + #[test] + fn test_dns_tcp_parser_split_payload() { + // A NULL flow. + let flow = std::ptr::null(); + + /* incomplete payload */ + #[rustfmt::skip] + let buf1: &[u8] = &[ + 0x00, 0x1c, 0x10, 0x32, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + /* complete payload plus the start of a new payload */ + #[rustfmt::skip] + let buf2: &[u8] = &[ + 0x00, 0x1c, 0x10, 0x32, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x03, + 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x10, 0x00, 0x01, + + // next. + 0x00, 0x1c, 0x10, 0x32, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + /* and the complete payload again with no trailing data. */ + #[rustfmt::skip] + let buf3: &[u8] = &[ + 0x00, 0x1c, 0x10, 0x32, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x06, 0x67, 0x6F, 0x6F, 0x67, 0x6C, 0x65, 0x03, + 0x63, 0x6F, 0x6D, 0x00, 0x00, 0x10, 0x00, 0x01, + ]; + + let mut state = DNSState::new(); + assert_eq!( + AppLayerResult::incomplete(0, 30), + state.parse_request_tcp(flow, StreamSlice::from_slice(buf1, STREAM_TOSERVER, 0)) + ); + assert_eq!( + AppLayerResult::incomplete(30, 30), + state.parse_request_tcp(flow, StreamSlice::from_slice(buf2, STREAM_TOSERVER, 0)) + ); + assert_eq!( + AppLayerResult::ok(), + state.parse_request_tcp(flow, StreamSlice::from_slice(buf3, STREAM_TOSERVER, 0)) + ); + } + + #[test] + fn test_dns_event_from_id() { + assert_eq!(DNSEvent::from_id(0), Some(DNSEvent::MalformedData)); + assert_eq!(DNSEvent::from_id(3), Some(DNSEvent::ZFlagSet)); + assert_eq!(DNSEvent::from_id(9), None); + } + + #[test] + fn test_dns_event_to_cstring() { + assert_eq!(DNSEvent::MalformedData.to_cstring(), "malformed_data\0"); + } + + #[test] + fn test_dns_event_from_string() { + let name = "malformed_data"; + let event = DNSEvent::from_string(name).unwrap(); + assert_eq!(event, DNSEvent::MalformedData); + assert_eq!(event.to_cstring(), format!("{}\0", name)); + } +} diff --git a/rust/src/dns/log.rs b/rust/src/dns/log.rs new file mode 100644 index 0000000..5212b1a --- /dev/null +++ b/rust/src/dns/log.rs @@ -0,0 +1,671 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std; +use std::collections::HashMap; +use std::string::String; + +use crate::dns::dns::*; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +pub const LOG_A: u64 = BIT_U64!(2); +pub const LOG_NS: u64 = BIT_U64!(3); +pub const LOG_MD: u64 = BIT_U64!(4); +pub const LOG_MF: u64 = BIT_U64!(5); +pub const LOG_CNAME: u64 = BIT_U64!(6); +pub const LOG_SOA: u64 = BIT_U64!(7); +pub const LOG_MB: u64 = BIT_U64!(8); +pub const LOG_MG: u64 = BIT_U64!(9); +pub const LOG_MR: u64 = BIT_U64!(10); +pub const LOG_NULL: u64 = BIT_U64!(11); +pub const LOG_WKS: u64 = BIT_U64!(12); +pub const LOG_PTR: u64 = BIT_U64!(13); +pub const LOG_HINFO: u64 = BIT_U64!(14); +pub const LOG_MINFO: u64 = BIT_U64!(15); +pub const LOG_MX: u64 = BIT_U64!(16); +pub const LOG_TXT: u64 = BIT_U64!(17); +pub const LOG_RP: u64 = BIT_U64!(18); +pub const LOG_AFSDB: u64 = BIT_U64!(19); +pub const LOG_X25: u64 = BIT_U64!(20); +pub const LOG_ISDN: u64 = BIT_U64!(21); +pub const LOG_RT: u64 = BIT_U64!(22); +pub const LOG_NSAP: u64 = BIT_U64!(23); +pub const LOG_NSAPPTR: u64 = BIT_U64!(24); +pub const LOG_SIG: u64 = BIT_U64!(25); +pub const LOG_KEY: u64 = BIT_U64!(26); +pub const LOG_PX: u64 = BIT_U64!(27); +pub const LOG_GPOS: u64 = BIT_U64!(28); +pub const LOG_AAAA: u64 = BIT_U64!(29); +pub const LOG_LOC: u64 = BIT_U64!(30); +pub const LOG_NXT: u64 = BIT_U64!(31); +pub const LOG_SRV: u64 = BIT_U64!(32); +pub const LOG_ATMA: u64 = BIT_U64!(33); +pub const LOG_NAPTR: u64 = BIT_U64!(34); +pub const LOG_KX: u64 = BIT_U64!(35); +pub const LOG_CERT: u64 = BIT_U64!(36); +pub const LOG_A6: u64 = BIT_U64!(37); +pub const LOG_DNAME: u64 = BIT_U64!(38); +pub const LOG_OPT: u64 = BIT_U64!(39); +pub const LOG_APL: u64 = BIT_U64!(40); +pub const LOG_DS: u64 = BIT_U64!(41); +pub const LOG_SSHFP: u64 = BIT_U64!(42); +pub const LOG_IPSECKEY: u64 = BIT_U64!(43); +pub const LOG_RRSIG: u64 = BIT_U64!(44); +pub const LOG_NSEC: u64 = BIT_U64!(45); +pub const LOG_DNSKEY: u64 = BIT_U64!(46); +pub const LOG_DHCID: u64 = BIT_U64!(47); +pub const LOG_NSEC3: u64 = BIT_U64!(48); +pub const LOG_NSEC3PARAM: u64 = BIT_U64!(49); +pub const LOG_TLSA: u64 = BIT_U64!(50); +pub const LOG_HIP: u64 = BIT_U64!(51); +pub const LOG_CDS: u64 = BIT_U64!(52); +pub const LOG_CDNSKEY: u64 = BIT_U64!(53); +pub const LOG_SPF: u64 = BIT_U64!(54); +pub const LOG_TKEY: u64 = BIT_U64!(55); +pub const LOG_TSIG: u64 = BIT_U64!(56); +pub const LOG_MAILA: u64 = BIT_U64!(57); +pub const LOG_ANY: u64 = BIT_U64!(58); +pub const LOG_URI: u64 = BIT_U64!(59); + +pub const LOG_FORMAT_GROUPED: u64 = BIT_U64!(60); +pub const LOG_FORMAT_DETAILED: u64 = BIT_U64!(61); +pub const LOG_HTTPS: u64 = BIT_U64!(62); + +fn dns_log_rrtype_enabled(rtype: u16, flags: u64) -> bool { + if flags == !0 { + return true; + } + + match rtype { + DNS_RECORD_TYPE_A => { + return flags & LOG_A != 0; + } + DNS_RECORD_TYPE_NS => { + return flags & LOG_NS != 0; + } + DNS_RECORD_TYPE_MD => { + return flags & LOG_MD != 0; + } + DNS_RECORD_TYPE_MF => { + return flags & LOG_MF != 0; + } + DNS_RECORD_TYPE_CNAME => { + return flags & LOG_CNAME != 0; + } + DNS_RECORD_TYPE_SOA => { + return flags & LOG_SOA != 0; + } + DNS_RECORD_TYPE_MB => { + return flags & LOG_MB != 0; + } + DNS_RECORD_TYPE_MG => { + return flags & LOG_MG != 0; + } + DNS_RECORD_TYPE_MR => { + return flags & LOG_MR != 0; + } + DNS_RECORD_TYPE_NULL => { + return flags & LOG_NULL != 0; + } + DNS_RECORD_TYPE_WKS => { + return flags & LOG_WKS != 0; + } + DNS_RECORD_TYPE_PTR => { + return flags & LOG_PTR != 0; + } + DNS_RECORD_TYPE_HINFO => { + return flags & LOG_HINFO != 0; + } + DNS_RECORD_TYPE_MINFO => { + return flags & LOG_MINFO != 0; + } + DNS_RECORD_TYPE_MX => { + return flags & LOG_MX != 0; + } + DNS_RECORD_TYPE_TXT => { + return flags & LOG_TXT != 0; + } + DNS_RECORD_TYPE_RP => { + return flags & LOG_RP != 0; + } + DNS_RECORD_TYPE_AFSDB => { + return flags & LOG_AFSDB != 0; + } + DNS_RECORD_TYPE_X25 => { + return flags & LOG_X25 != 0; + } + DNS_RECORD_TYPE_ISDN => { + return flags & LOG_ISDN != 0; + } + DNS_RECORD_TYPE_RT => { + return flags & LOG_RT != 0; + } + DNS_RECORD_TYPE_NSAP => { + return flags & LOG_NSAP != 0; + } + DNS_RECORD_TYPE_NSAPPTR => { + return flags & LOG_NSAPPTR != 0; + } + DNS_RECORD_TYPE_SIG => { + return flags & LOG_SIG != 0; + } + DNS_RECORD_TYPE_KEY => { + return flags & LOG_KEY != 0; + } + DNS_RECORD_TYPE_PX => { + return flags & LOG_PX != 0; + } + DNS_RECORD_TYPE_GPOS => { + return flags & LOG_GPOS != 0; + } + DNS_RECORD_TYPE_AAAA => { + return flags & LOG_AAAA != 0; + } + DNS_RECORD_TYPE_LOC => { + return flags & LOG_LOC != 0; + } + DNS_RECORD_TYPE_NXT => { + return flags & LOG_NXT != 0; + } + DNS_RECORD_TYPE_SRV => { + return flags & LOG_SRV != 0; + } + DNS_RECORD_TYPE_ATMA => { + return flags & LOG_ATMA != 0; + } + DNS_RECORD_TYPE_NAPTR => { + return flags & LOG_NAPTR != 0; + } + DNS_RECORD_TYPE_KX => { + return flags & LOG_KX != 0; + } + DNS_RECORD_TYPE_CERT => { + return flags & LOG_CERT != 0; + } + DNS_RECORD_TYPE_A6 => { + return flags & LOG_A6 != 0; + } + DNS_RECORD_TYPE_DNAME => { + return flags & LOG_DNAME != 0; + } + DNS_RECORD_TYPE_OPT => { + return flags & LOG_OPT != 0; + } + DNS_RECORD_TYPE_APL => { + return flags & LOG_APL != 0; + } + DNS_RECORD_TYPE_DS => { + return flags & LOG_DS != 0; + } + DNS_RECORD_TYPE_SSHFP => { + return flags & LOG_SSHFP != 0; + } + DNS_RECORD_TYPE_IPSECKEY => { + return flags & LOG_IPSECKEY != 0; + } + DNS_RECORD_TYPE_RRSIG => { + return flags & LOG_RRSIG != 0; + } + DNS_RECORD_TYPE_NSEC => { + return flags & LOG_NSEC != 0; + } + DNS_RECORD_TYPE_DNSKEY => { + return flags & LOG_DNSKEY != 0; + } + DNS_RECORD_TYPE_DHCID => { + return flags & LOG_DHCID != 0; + } + DNS_RECORD_TYPE_NSEC3 => return flags & LOG_NSEC3 != 0, + DNS_RECORD_TYPE_NSEC3PARAM => { + return flags & LOG_NSEC3PARAM != 0; + } + DNS_RECORD_TYPE_TLSA => { + return flags & LOG_TLSA != 0; + } + DNS_RECORD_TYPE_HIP => { + return flags & LOG_HIP != 0; + } + DNS_RECORD_TYPE_CDS => { + return flags & LOG_CDS != 0; + } + DNS_RECORD_TYPE_CDNSKEY => { + return flags & LOG_CDNSKEY != 0; + } + DNS_RECORD_TYPE_HTTPS => { + return flags & LOG_HTTPS != 0; + } + DNS_RECORD_TYPE_SPF => { + return flags & LOG_SPF != 0; + } + DNS_RECORD_TYPE_TKEY => { + return flags & LOG_TKEY != 0; + } + DNS_RECORD_TYPE_TSIG => { + return flags & LOG_TSIG != 0; + } + DNS_RECORD_TYPE_MAILA => { + return flags & LOG_MAILA != 0; + } + DNS_RECORD_TYPE_ANY => { + return flags & LOG_ANY != 0; + } + DNS_RECORD_TYPE_URI => { + return flags & LOG_URI != 0; + } + _ => { + return false; + } + } +} + +pub fn dns_rrtype_string(rrtype: u16) -> String { + match rrtype { + DNS_RECORD_TYPE_A => "A", + DNS_RECORD_TYPE_NS => "NS", + DNS_RECORD_TYPE_AAAA => "AAAA", + DNS_RECORD_TYPE_CNAME => "CNAME", + DNS_RECORD_TYPE_TXT => "TXT", + DNS_RECORD_TYPE_MX => "MX", + DNS_RECORD_TYPE_SOA => "SOA", + DNS_RECORD_TYPE_PTR => "PTR", + DNS_RECORD_TYPE_SIG => "SIG", + DNS_RECORD_TYPE_KEY => "KEY", + DNS_RECORD_TYPE_WKS => "WKS", + DNS_RECORD_TYPE_TKEY => "TKEY", + DNS_RECORD_TYPE_TSIG => "TSIG", + DNS_RECORD_TYPE_ANY => "ANY", + DNS_RECORD_TYPE_RRSIG => "RRSIG", + DNS_RECORD_TYPE_NSEC => "NSEC", + DNS_RECORD_TYPE_DNSKEY => "DNSKEY", + DNS_RECORD_TYPE_HINFO => "HINFO", + DNS_RECORD_TYPE_MINFO => "MINFO", + DNS_RECORD_TYPE_RP => "RP", + DNS_RECORD_TYPE_AFSDB => "AFSDB", + DNS_RECORD_TYPE_X25 => "X25", + DNS_RECORD_TYPE_ISDN => "ISDN", + DNS_RECORD_TYPE_RT => "RT", + DNS_RECORD_TYPE_NSAP => "NSAP", + DNS_RECORD_TYPE_NSAPPTR => "NSAPPT", + DNS_RECORD_TYPE_PX => "PX", + DNS_RECORD_TYPE_GPOS => "GPOS", + DNS_RECORD_TYPE_LOC => "LOC", + DNS_RECORD_TYPE_SRV => "SRV", + DNS_RECORD_TYPE_ATMA => "ATMA", + DNS_RECORD_TYPE_NAPTR => "NAPTR", + DNS_RECORD_TYPE_KX => "KX", + DNS_RECORD_TYPE_CERT => "CERT", + DNS_RECORD_TYPE_A6 => "A6", + DNS_RECORD_TYPE_DNAME => "DNAME", + DNS_RECORD_TYPE_OPT => "OPT", + DNS_RECORD_TYPE_APL => "APL", + DNS_RECORD_TYPE_DS => "DS", + DNS_RECORD_TYPE_SSHFP => "SSHFP", + DNS_RECORD_TYPE_IPSECKEY => "IPSECKEY", + DNS_RECORD_TYPE_DHCID => "DHCID", + DNS_RECORD_TYPE_NSEC3 => "NSEC3", + DNS_RECORD_TYPE_NSEC3PARAM => "NSEC3PARAM", + DNS_RECORD_TYPE_TLSA => "TLSA", + DNS_RECORD_TYPE_HIP => "HIP", + DNS_RECORD_TYPE_CDS => "CDS", + DNS_RECORD_TYPE_CDNSKEY => "CDSNKEY", + DNS_RECORD_TYPE_HTTPS => "HTTPS", + DNS_RECORD_TYPE_MAILA => "MAILA", + DNS_RECORD_TYPE_URI => "URI", + DNS_RECORD_TYPE_MB => "MB", + DNS_RECORD_TYPE_MG => "MG", + DNS_RECORD_TYPE_MR => "MR", + DNS_RECORD_TYPE_NULL => "NULL", + DNS_RECORD_TYPE_SPF => "SPF", + DNS_RECORD_TYPE_NXT => "NXT", + DNS_RECORD_TYPE_MD => "ND", + DNS_RECORD_TYPE_MF => "MF", + _ => { + return rrtype.to_string(); + } + } + .to_string() +} + +pub fn dns_rcode_string(flags: u16) -> String { + match flags & 0x000f { + DNS_RCODE_NOERROR => "NOERROR", + DNS_RCODE_FORMERR => "FORMERR", + DNS_RCODE_SERVFAIL => "SERVFAIL", + DNS_RCODE_NXDOMAIN => "NXDOMAIN", + DNS_RCODE_NOTIMP => "NOTIMP", + DNS_RCODE_REFUSED => "REFUSED", + DNS_RCODE_YXDOMAIN => "YXDOMAIN", + DNS_RCODE_YXRRSET => "YXRRSET", + DNS_RCODE_NXRRSET => "NXRRSET", + DNS_RCODE_NOTAUTH => "NOTAUTH", + DNS_RCODE_NOTZONE => "NOTZONE", + DNS_RCODE_BADVERS => "BADVERS/BADSIG", + DNS_RCODE_BADKEY => "BADKEY", + DNS_RCODE_BADTIME => "BADTIME", + DNS_RCODE_BADMODE => "BADMODE", + DNS_RCODE_BADNAME => "BADNAME", + DNS_RCODE_BADALG => "BADALG", + DNS_RCODE_BADTRUNC => "BADTRUNC", + _ => { + return (flags & 0x000f).to_string(); + } + } + .to_string() +} + +/// Format bytes as an IP address string. +pub fn dns_print_addr(addr: &Vec<u8>) -> std::string::String { + if addr.len() == 4 { + return format!("{}.{}.{}.{}", addr[0], addr[1], addr[2], addr[3]); + } else if addr.len() == 16 { + return format!("{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}:{:02x}{:02x}", + addr[0], + addr[1], + addr[2], + addr[3], + addr[4], + addr[5], + addr[6], + addr[7], + addr[8], + addr[9], + addr[10], + addr[11], + addr[12], + addr[13], + addr[14], + addr[15]); + } else { + return "".to_string(); + } +} + +/// Log SOA section fields. +fn dns_log_soa(soa: &DNSRDataSOA) -> Result<JsonBuilder, JsonError> { + let mut js = JsonBuilder::try_new_object()?; + + js.set_string_from_bytes("mname", &soa.mname)?; + js.set_string_from_bytes("rname", &soa.rname)?; + js.set_uint("serial", soa.serial as u64)?; + js.set_uint("refresh", soa.refresh as u64)?; + js.set_uint("retry", soa.retry as u64)?; + js.set_uint("expire", soa.expire as u64)?; + js.set_uint("minimum", soa.minimum as u64)?; + + js.close()?; + return Ok(js); +} + +/// Log SSHFP section fields. +fn dns_log_sshfp(sshfp: &DNSRDataSSHFP) -> Result<JsonBuilder, JsonError> { + let mut js = JsonBuilder::try_new_object()?; + + let mut hex = Vec::new(); + for byte in &sshfp.fingerprint { + hex.push(format!("{:02x}", byte)); + } + + js.set_string("fingerprint", &hex.join(":"))?; + js.set_uint("algo", sshfp.algo as u64)?; + js.set_uint("type", sshfp.fp_type as u64)?; + + js.close()?; + return Ok(js); +} + +/// Log SRV section fields. +fn dns_log_srv(srv: &DNSRDataSRV) -> Result<JsonBuilder, JsonError> { + let mut js = JsonBuilder::try_new_object()?; + + js.set_uint("priority", srv.priority as u64)?; + js.set_uint("weight", srv.weight as u64)?; + js.set_uint("port", srv.port as u64)?; + js.set_string_from_bytes("name", &srv.target)?; + + js.close()?; + return Ok(js); +} + +fn dns_log_json_answer_detail(answer: &DNSAnswerEntry) -> Result<JsonBuilder, JsonError> { + let mut jsa = JsonBuilder::try_new_object()?; + + jsa.set_string_from_bytes("rrname", &answer.name)?; + jsa.set_string("rrtype", &dns_rrtype_string(answer.rrtype))?; + jsa.set_uint("ttl", answer.ttl as u64)?; + + match &answer.data { + DNSRData::A(addr) | DNSRData::AAAA(addr) => { + jsa.set_string("rdata", &dns_print_addr(addr))?; + } + DNSRData::CNAME(bytes) + | DNSRData::MX(bytes) + | DNSRData::NS(bytes) + | DNSRData::TXT(bytes) + | DNSRData::NULL(bytes) + | DNSRData::PTR(bytes) => { + jsa.set_string_from_bytes("rdata", bytes)?; + } + DNSRData::SOA(soa) => { + jsa.set_object("soa", &dns_log_soa(soa)?)?; + } + DNSRData::SSHFP(sshfp) => { + jsa.set_object("sshfp", &dns_log_sshfp(sshfp)?)?; + } + DNSRData::SRV(srv) => { + jsa.set_object("srv", &dns_log_srv(srv)?)?; + } + _ => {} + } + + jsa.close()?; + return Ok(jsa); +} + +fn dns_log_json_answer( + js: &mut JsonBuilder, response: &DNSResponse, flags: u64, +) -> Result<(), JsonError> { + let header = &response.header; + + js.set_uint("version", 2)?; + js.set_string("type", "answer")?; + js.set_uint("id", header.tx_id as u64)?; + js.set_string("flags", format!("{:x}", header.flags).as_str())?; + if header.flags & 0x8000 != 0 { + js.set_bool("qr", true)?; + } + if header.flags & 0x0400 != 0 { + js.set_bool("aa", true)?; + } + if header.flags & 0x0200 != 0 { + js.set_bool("tc", true)?; + } + if header.flags & 0x0100 != 0 { + js.set_bool("rd", true)?; + } + if header.flags & 0x0080 != 0 { + js.set_bool("ra", true)?; + } + if header.flags & 0x0040 != 0 { + js.set_bool("z", true)?; + } + + let opcode = ((header.flags >> 11) & 0xf) as u8; + js.set_uint("opcode", opcode as u64)?; + + if let Some(query) = response.queries.first() { + js.set_string_from_bytes("rrname", &query.name)?; + js.set_string("rrtype", &dns_rrtype_string(query.rrtype))?; + } + js.set_string("rcode", &dns_rcode_string(header.flags))?; + + if !response.answers.is_empty() { + let mut js_answers = JsonBuilder::try_new_array()?; + + // For grouped answers we use a HashMap keyed by the rrtype. + let mut answer_types = HashMap::new(); + + for answer in &response.answers { + if flags & LOG_FORMAT_GROUPED != 0 { + let type_string = dns_rrtype_string(answer.rrtype); + match &answer.data { + DNSRData::A(addr) | DNSRData::AAAA(addr) => { + if !answer_types.contains_key(&type_string) { + answer_types.insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_string(&dns_print_addr(addr))?; + } + } + DNSRData::CNAME(bytes) + | DNSRData::MX(bytes) + | DNSRData::NS(bytes) + | DNSRData::TXT(bytes) + | DNSRData::NULL(bytes) + | DNSRData::PTR(bytes) => { + if !answer_types.contains_key(&type_string) { + answer_types.insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_string_from_bytes(bytes)?; + } + } + DNSRData::SOA(soa) => { + if !answer_types.contains_key(&type_string) { + answer_types.insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_object(&dns_log_soa(soa)?)?; + } + } + DNSRData::SSHFP(sshfp) => { + if !answer_types.contains_key(&type_string) { + answer_types.insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_object(&dns_log_sshfp(sshfp)?)?; + } + } + DNSRData::SRV(srv) => { + if !answer_types.contains_key(&type_string) { + answer_types.insert(type_string.to_string(), JsonBuilder::try_new_array()?); + } + if let Some(a) = answer_types.get_mut(&type_string) { + a.append_object(&dns_log_srv(srv)?)?; + } + } + _ => {} + } + } + + if flags & LOG_FORMAT_DETAILED != 0 { + js_answers.append_object(&dns_log_json_answer_detail(answer)?)?; + } + } + + js_answers.close()?; + + if flags & LOG_FORMAT_DETAILED != 0 { + js.set_object("answers", &js_answers)?; + } + + if flags & LOG_FORMAT_GROUPED != 0 { + js.open_object("grouped")?; + for (k, mut v) in answer_types.drain() { + v.close()?; + js.set_object(&k, &v)?; + } + js.close()?; + } + } + + if !response.authorities.is_empty() { + js.open_array("authorities")?; + for auth in &response.authorities { + let auth_detail = dns_log_json_answer_detail(auth)?; + js.append_object(&auth_detail)?; + } + js.close()?; + } + + Ok(()) +} + +fn dns_log_query( + tx: &mut DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, +) -> Result<bool, JsonError> { + let index = i as usize; + if let Some(request) = &tx.request { + if index < request.queries.len() { + let query = &request.queries[index]; + if dns_log_rrtype_enabled(query.rrtype, flags) { + jb.set_string("type", "query")?; + jb.set_uint("id", request.header.tx_id as u64)?; + jb.set_string_from_bytes("rrname", &query.name)?; + jb.set_string("rrtype", &dns_rrtype_string(query.rrtype))?; + jb.set_uint("tx_id", tx.id - 1)?; + if request.header.flags & 0x0040 != 0 { + jb.set_bool("z", true)?; + } + let opcode = ((request.header.flags >> 11) & 0xf) as u8; + jb.set_uint("opcode", opcode as u64)?; + return Ok(true); + } + } + } + + return Ok(false); +} + +#[no_mangle] +pub extern "C" fn rs_dns_log_json_query( + tx: &mut DNSTransaction, i: u16, flags: u64, jb: &mut JsonBuilder, +) -> bool { + match dns_log_query(tx, i, flags, jb) { + Ok(false) | Err(_) => { + return false; + } + Ok(true) => { + return true; + } + } +} + +#[no_mangle] +pub extern "C" fn rs_dns_log_json_answer( + tx: &mut DNSTransaction, flags: u64, js: &mut JsonBuilder, +) -> bool { + if let Some(response) = &tx.response { + for query in &response.queries { + if dns_log_rrtype_enabled(query.rrtype, flags) { + return dns_log_json_answer(js, response, flags).is_ok(); + } + } + } + return false; +} + +#[no_mangle] +pub extern "C" fn rs_dns_do_log_answer(tx: &mut DNSTransaction, flags: u64) -> bool { + if let Some(response) = &tx.response { + for query in &response.queries { + if dns_log_rrtype_enabled(query.rrtype, flags) { + return true; + } + } + } + return false; +} diff --git a/rust/src/dns/lua.rs b/rust/src/dns/lua.rs new file mode 100644 index 0000000..b9935f8 --- /dev/null +++ b/rust/src/dns/lua.rs @@ -0,0 +1,234 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::os::raw::c_int; + +use crate::dns::dns::*; +use crate::dns::log::*; +use crate::lua::*; + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_tx_id(clua: &mut CLuaState, tx: &mut DNSTransaction) { + let lua = LuaState { lua: clua }; + + lua.pushinteger(tx.tx_id() as i64); +} + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_rrname(clua: &mut CLuaState, tx: &mut DNSTransaction) -> c_int { + let lua = LuaState { lua: clua }; + + if let Some(request) = &tx.request { + if let Some(query) = request.queries.first() { + lua.pushstring(&String::from_utf8_lossy(&query.name)); + return 1; + } + } else if let Some(response) = &tx.response { + if let Some(query) = response.queries.first() { + lua.pushstring(&String::from_utf8_lossy(&query.name)); + return 1; + } + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_rcode(clua: &mut CLuaState, tx: &mut DNSTransaction) -> c_int { + let lua = LuaState { lua: clua }; + + let rcode = tx.rcode(); + if rcode > 0 { + lua.pushstring(&dns_rcode_string(rcode)); + return 1; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_query_table( + clua: &mut CLuaState, tx: &mut DNSTransaction, +) -> c_int { + let lua = LuaState { lua: clua }; + + let mut i: i64 = 0; + + // Create table now to be consistent with C that always returns + // table even in the absence of any authorities. + lua.newtable(); + + // We first look in the request for queries. However, if there is + // no request, check the response for queries. + if let Some(request) = &tx.request { + for query in &request.queries { + lua.pushinteger(i); + i += 1; + + lua.newtable(); + + lua.pushstring("type"); + lua.pushstring(&dns_rrtype_string(query.rrtype)); + lua.settable(-3); + + lua.pushstring("rrname"); + lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.settable(-3); + + lua.settable(-3); + } + } else if let Some(response) = &tx.response { + for query in &response.queries { + lua.pushinteger(i); + i += 1; + + lua.newtable(); + + lua.pushstring("type"); + lua.pushstring(&dns_rrtype_string(query.rrtype)); + lua.settable(-3); + + lua.pushstring("rrname"); + lua.pushstring(&String::from_utf8_lossy(&query.name)); + lua.settable(-3); + + lua.settable(-3); + } + } + + // Again, always return 1 to be consistent with C, even if the + // table is empty. + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_answer_table( + clua: &mut CLuaState, tx: &mut DNSTransaction, +) -> c_int { + let lua = LuaState { lua: clua }; + + let mut i: i64 = 0; + + // Create table now to be consistent with C that always returns + // table even in the absence of any authorities. + lua.newtable(); + + if let Some(response) = &tx.response { + for answer in &response.answers { + lua.pushinteger(i); + i += 1; + + lua.newtable(); + lua.pushstring("type"); + lua.pushstring(&dns_rrtype_string(answer.rrtype)); + lua.settable(-3); + + lua.pushstring("ttl"); + lua.pushinteger(answer.ttl as i64); + lua.settable(-3); + + lua.pushstring("rrname"); + lua.pushstring(&String::from_utf8_lossy(&answer.name)); + lua.settable(-3); + + // All rdata types are pushed to "addr" for backwards compatibility + match answer.data { + DNSRData::A(ref bytes) | DNSRData::AAAA(ref bytes) => { + if !bytes.is_empty() { + lua.pushstring("addr"); + lua.pushstring(&dns_print_addr(bytes)); + lua.settable(-3); + } + } + DNSRData::CNAME(ref bytes) + | DNSRData::MX(ref bytes) + | DNSRData::NS(ref bytes) + | DNSRData::TXT(ref bytes) + | DNSRData::NULL(ref bytes) + | DNSRData::PTR(ref bytes) + | DNSRData::Unknown(ref bytes) => { + if !bytes.is_empty() { + lua.pushstring("addr"); + lua.pushstring(&String::from_utf8_lossy(bytes)); + lua.settable(-3); + } + } + DNSRData::SOA(ref soa) => { + if !soa.mname.is_empty() { + lua.pushstring("addr"); + lua.pushstring(&String::from_utf8_lossy(&soa.mname)); + lua.settable(-3); + } + } + DNSRData::SSHFP(ref sshfp) => { + lua.pushstring("addr"); + lua.pushstring(&String::from_utf8_lossy(&sshfp.fingerprint)); + lua.settable(-3); + } + DNSRData::SRV(ref srv) => { + lua.pushstring("addr"); + lua.pushstring(&String::from_utf8_lossy(&srv.target)); + lua.settable(-3); + } + } + lua.settable(-3); + } + } + + // Again, always return 1 to be consistent with C, even if the + // table is empty. + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_dns_lua_get_authority_table( + clua: &mut CLuaState, tx: &mut DNSTransaction, +) -> c_int { + let lua = LuaState { lua: clua }; + + let mut i: i64 = 0; + + // Create table now to be consistent with C that always returns + // table even in the absence of any authorities. + lua.newtable(); + + if let Some(response) = &tx.response { + for answer in &response.authorities { + lua.pushinteger(i); + i += 1; + + lua.newtable(); + lua.pushstring("type"); + lua.pushstring(&dns_rrtype_string(answer.rrtype)); + lua.settable(-3); + + lua.pushstring("ttl"); + lua.pushinteger(answer.ttl as i64); + lua.settable(-3); + + lua.pushstring("rrname"); + lua.pushstring(&String::from_utf8_lossy(&answer.name)); + lua.settable(-3); + + lua.settable(-3); + } + } + + // Again, always return 1 to be consistent with C, even if the + // table is empty. + return 1; +} diff --git a/rust/src/dns/mod.rs b/rust/src/dns/mod.rs new file mode 100644 index 0000000..b0ca00f --- /dev/null +++ b/rust/src/dns/mod.rs @@ -0,0 +1,26 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! DNS parser, detection, logger and application layer module. + +pub mod detect; +pub mod dns; +pub mod log; +pub mod parser; + +#[cfg(feature = "lua")] +pub mod lua; diff --git a/rust/src/dns/parser.rs b/rust/src/dns/parser.rs new file mode 100644 index 0000000..a1d97a5 --- /dev/null +++ b/rust/src/dns/parser.rs @@ -0,0 +1,851 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for DNS. + +use crate::dns::dns::*; +use nom7::combinator::{complete, rest}; +use nom7::error::ErrorKind; +use nom7::multi::{count, length_data, many_m_n}; +use nom7::number::streaming::{be_u16, be_u32, be_u8}; +use nom7::{error_position, Err, IResult}; + +// Parse a DNS header. +pub fn dns_parse_header(i: &[u8]) -> IResult<&[u8], DNSHeader> { + let (i, tx_id) = be_u16(i)?; + let (i, flags) = be_u16(i)?; + let (i, questions) = be_u16(i)?; + let (i, answer_rr) = be_u16(i)?; + let (i, authority_rr) = be_u16(i)?; + let (i, additional_rr) = be_u16(i)?; + Ok(( + i, + DNSHeader { + tx_id, + flags, + questions, + answer_rr, + authority_rr, + additional_rr, + }, + )) +} + +/// Parse a DNS name. +/// +/// Parameters: +/// start: the start of the name +/// message: the complete message that start is a part of with the DNS header +pub fn dns_parse_name<'b>(start: &'b [u8], message: &'b [u8]) -> IResult<&'b [u8], Vec<u8>> { + let mut pos = start; + let mut pivot = start; + let mut name: Vec<u8> = Vec::with_capacity(32); + let mut count = 0; + + loop { + if pos.is_empty() { + break; + } + + let len = pos[0]; + + if len == 0x00 { + pos = &pos[1..]; + break; + } else if len & 0b1100_0000 == 0 { + let (rem, label) = length_data(be_u8)(pos)?; + if !name.is_empty() { + name.push(b'.'); + } + name.extend(label); + pos = rem; + } else if len & 0b1100_0000 == 0b1100_0000 { + let (rem, leader) = be_u16(pos)?; + let offset = usize::from(leader) & 0x3fff; + if offset > message.len() { + return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); + } + pos = &message[offset..]; + if pivot == start { + pivot = rem; + } + } else { + return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); + } + + // Return error if we've looped a certain number of times. + count += 1; + if count > 255 { + return Err(Err::Error(error_position!(pos, ErrorKind::OctDigit))); + } + } + + // If we followed a pointer we return the position after the first + // pointer followed. Is there a better way to see if these slices + // diverged from each other? A straight up comparison would + // actually check the contents. + if pivot.len() != start.len() { + return Ok((pivot, name)); + } + return Ok((pos, name)); +} + +/// Parse answer entries. +/// +/// In keeping with the C implementation, answer values that can +/// contain multiple answers get expanded into their own answer +/// records. An example of this is a TXT record with multiple strings +/// in it - each string will be expanded to its own answer record. +/// +/// This function could be a made a whole lot simpler if we logged a +/// multi-string TXT entry as a single quote string, similar to the +/// output of dig. Something to consider for a future version. +fn dns_parse_answer<'a>( + slice: &'a [u8], message: &'a [u8], count: usize, +) -> IResult<&'a [u8], Vec<DNSAnswerEntry>> { + let mut answers = Vec::new(); + let mut input = slice; + + struct Answer<'a> { + name: Vec<u8>, + rrtype: u16, + rrclass: u16, + ttl: u32, + data: &'a [u8], + } + + fn subparser<'a>(i: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], Answer<'a>> { + let (i, name) = dns_parse_name(i, message)?; + let (i, rrtype) = be_u16(i)?; + let (i, rrclass) = be_u16(i)?; + let (i, ttl) = be_u32(i)?; + let (i, data) = length_data(be_u16)(i)?; + let answer = Answer { + name, + rrtype, + rrclass, + ttl, + data, + }; + Ok((i, answer)) + } + + for _ in 0..count { + match subparser(input, message) { + Ok((rem, val)) => { + let n = match val.rrtype { + DNS_RECORD_TYPE_TXT => { + // For TXT records we need to run the parser + // multiple times. Set n high, to the maximum + // value based on a max txt side of 65535, but + // taking into considering that strings need + // to be quoted, so half that. + 32767 + } + _ => { + // For all other types we only want to run the + // parser once, so set n to 1. + 1 + } + }; + let result: IResult<&'a [u8], Vec<DNSRData>> = + many_m_n(1, n, complete(|b| dns_parse_rdata(b, message, val.rrtype)))(val.data); + match result { + Ok((_, rdatas)) => { + for rdata in rdatas { + answers.push(DNSAnswerEntry { + name: val.name.clone(), + rrtype: val.rrtype, + rrclass: val.rrclass, + ttl: val.ttl, + data: rdata, + }); + } + } + Err(e) => { + return Err(e); + } + } + input = rem; + } + Err(e) => { + return Err(e); + } + } + } + + return Ok((input, answers)); +} + +pub fn dns_parse_response_body<'a>( + i: &'a [u8], message: &'a [u8], header: DNSHeader, +) -> IResult<&'a [u8], DNSResponse> { + let (i, queries) = count(|b| dns_parse_query(b, message), header.questions as usize)(i)?; + let (i, answers) = dns_parse_answer(i, message, header.answer_rr as usize)?; + let (i, authorities) = dns_parse_answer(i, message, header.authority_rr as usize)?; + Ok(( + i, + DNSResponse { + header, + queries, + answers, + authorities, + }, + )) +} + +/// Parse a single DNS query. +/// +/// Arguments are suitable for using with call!: +/// +/// call!(complete_dns_message_buffer) +pub fn dns_parse_query<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSQueryEntry> { + let i = input; + let (i, name) = dns_parse_name(i, message)?; + let (i, rrtype) = be_u16(i)?; + let (i, rrclass) = be_u16(i)?; + Ok(( + i, + DNSQueryEntry { + name, + rrtype, + rrclass, + }, + )) +} + +fn dns_parse_rdata_a(input: &[u8]) -> IResult<&[u8], DNSRData> { + rest(input).map(|(input, data)| (input, DNSRData::A(data.to_vec()))) +} + +fn dns_parse_rdata_aaaa(input: &[u8]) -> IResult<&[u8], DNSRData> { + rest(input).map(|(input, data)| (input, DNSRData::AAAA(data.to_vec()))) +} + +fn dns_parse_rdata_cname<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::CNAME(name))) +} + +fn dns_parse_rdata_ns<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::NS(name))) +} + +fn dns_parse_rdata_ptr<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + dns_parse_name(input, message).map(|(input, name)| (input, DNSRData::PTR(name))) +} + +fn dns_parse_rdata_soa<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + let i = input; + let (i, mname) = dns_parse_name(i, message)?; + let (i, rname) = dns_parse_name(i, message)?; + let (i, serial) = be_u32(i)?; + let (i, refresh) = be_u32(i)?; + let (i, retry) = be_u32(i)?; + let (i, expire) = be_u32(i)?; + let (i, minimum) = be_u32(i)?; + Ok(( + i, + DNSRData::SOA(DNSRDataSOA { + mname, + rname, + serial, + refresh, + retry, + expire, + minimum, + }), + )) +} + +fn dns_parse_rdata_mx<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + // For MX we skip over the preference field before + // parsing out the name. + let (i, _) = be_u16(input)?; + let (i, name) = dns_parse_name(i, message)?; + Ok((i, DNSRData::MX(name))) +} + +fn dns_parse_rdata_srv<'a>(input: &'a [u8], message: &'a [u8]) -> IResult<&'a [u8], DNSRData> { + let i = input; + let (i, priority) = be_u16(i)?; + let (i, weight) = be_u16(i)?; + let (i, port) = be_u16(i)?; + let (i, target) = dns_parse_name(i, message)?; + Ok(( + i, + DNSRData::SRV(DNSRDataSRV { + priority, + weight, + port, + target, + }), + )) +} + +fn dns_parse_rdata_txt(input: &[u8]) -> IResult<&[u8], DNSRData> { + let (i, txt) = length_data(be_u8)(input)?; + Ok((i, DNSRData::TXT(txt.to_vec()))) +} + +fn dns_parse_rdata_null(input: &[u8]) -> IResult<&[u8], DNSRData> { + rest(input).map(|(input, data)| (input, DNSRData::NULL(data.to_vec()))) +} + +fn dns_parse_rdata_sshfp(input: &[u8]) -> IResult<&[u8], DNSRData> { + let i = input; + let (i, algo) = be_u8(i)?; + let (i, fp_type) = be_u8(i)?; + let fingerprint = i; + Ok(( + &[], + DNSRData::SSHFP(DNSRDataSSHFP { + algo, + fp_type, + fingerprint: fingerprint.to_vec(), + }), + )) +} + +fn dns_parse_rdata_unknown(input: &[u8]) -> IResult<&[u8], DNSRData> { + rest(input).map(|(input, data)| (input, DNSRData::Unknown(data.to_vec()))) +} + +pub fn dns_parse_rdata<'a>( + input: &'a [u8], message: &'a [u8], rrtype: u16, +) -> IResult<&'a [u8], DNSRData> { + match rrtype { + DNS_RECORD_TYPE_A => dns_parse_rdata_a(input), + DNS_RECORD_TYPE_AAAA => dns_parse_rdata_aaaa(input), + DNS_RECORD_TYPE_CNAME => dns_parse_rdata_cname(input, message), + DNS_RECORD_TYPE_PTR => dns_parse_rdata_ptr(input, message), + DNS_RECORD_TYPE_SOA => dns_parse_rdata_soa(input, message), + DNS_RECORD_TYPE_MX => dns_parse_rdata_mx(input, message), + DNS_RECORD_TYPE_NS => dns_parse_rdata_ns(input, message), + DNS_RECORD_TYPE_TXT => dns_parse_rdata_txt(input), + DNS_RECORD_TYPE_NULL => dns_parse_rdata_null(input), + DNS_RECORD_TYPE_SSHFP => dns_parse_rdata_sshfp(input), + DNS_RECORD_TYPE_SRV => dns_parse_rdata_srv(input, message), + _ => dns_parse_rdata_unknown(input), + } +} + +/// Parse a DNS request. +pub fn dns_parse_request(input: &[u8]) -> IResult<&[u8], DNSRequest> { + let i = input; + let (i, header) = dns_parse_header(i)?; + dns_parse_request_body(i, input, header) +} + +pub fn dns_parse_request_body<'a>( + input: &'a [u8], message: &'a [u8], header: DNSHeader, +) -> IResult<&'a [u8], DNSRequest> { + let i = input; + let (i, queries) = count(|b| dns_parse_query(b, message), header.questions as usize)(i)?; + Ok((i, DNSRequest { header, queries })) +} + +#[cfg(test)] +mod tests { + + use crate::dns::dns::{DNSAnswerEntry, DNSHeader}; + use crate::dns::parser::*; + + /// Parse a simple name with no pointers. + #[test] + fn test_dns_parse_name() { + let buf: &[u8] = &[ + 0x09, 0x63, /* .......c */ + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2d, 0x63, 0x66, /* lient-cf */ + 0x07, 0x64, 0x72, 0x6f, 0x70, 0x62, 0x6f, 0x78, /* .dropbox */ + 0x03, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x01, 0x00, /* .com.... */ + ]; + let expected_remainder: &[u8] = &[0x00, 0x01, 0x00]; + let (remainder, name) = dns_parse_name(buf, buf).unwrap(); + assert_eq!("client-cf.dropbox.com".as_bytes(), &name[..]); + assert_eq!(remainder, expected_remainder); + } + + /// Test parsing a name with pointers. + #[test] + fn test_dns_parse_name_with_pointer() { + let buf: &[u8] = &[ + 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0 - .....F.. */ + 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8 - ......E. */ + 0x00, 0x7b, 0x71, 0x6e, 0x00, 0x00, 0x39, 0x11, /* 16 - .{qn..9. */ + 0xf4, 0xd9, 0x08, 0x08, 0x08, 0x08, 0x0a, 0x10, /* 24 - ........ */ + 0x01, 0x0b, 0x00, 0x35, 0xe1, 0x8e, 0x00, 0x67, /* 32 - ...5...g */ + 0x60, 0x00, 0xef, 0x08, 0x81, 0x80, 0x00, 0x01, /* 40 - `....... */ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* 48 - .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* 56 - ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* 64 - ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* 72 - org..... */ + 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* 80 - ........ */ + 0x0e, 0x0f, 0x00, 0x02, 0xc0, 0x10, 0xc0, 0x10, /* 88 - ........ */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 96 - .......+ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, 0xc0, 0x10, /* 104 - ....N... */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x01, 0x2b, /* 112 - .......+ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0x00, 0x00, /* 120 - ....N... */ + 0x29, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* 128 - )....... */ + 0x00, /* 136 - . */ + ]; + + // The DNS payload starts at offset 42. + let message = &buf[42..]; + + // The name at offset 54 is the complete name. + let start1 = &buf[54..]; + let res1 = dns_parse_name(start1, message); + assert_eq!( + res1, + Ok((&start1[22..], "www.suricata-ids.org".as_bytes().to_vec())) + ); + + // The second name starts at offset 80, but is just a pointer + // to the first. + let start2 = &buf[80..]; + let res2 = dns_parse_name(start2, message); + assert_eq!( + res2, + Ok((&start2[2..], "www.suricata-ids.org".as_bytes().to_vec())) + ); + + // The third name starts at offset 94, but is a pointer to a + // portion of the first. + let start3 = &buf[94..]; + let res3 = dns_parse_name(start3, message); + assert_eq!( + res3, + Ok((&start3[2..], "suricata-ids.org".as_bytes().to_vec())) + ); + + // The fourth name starts at offset 110, but is a pointer to a + // portion of the first. + let start4 = &buf[110..]; + let res4 = dns_parse_name(start4, message); + assert_eq!( + res4, + Ok((&start4[2..], "suricata-ids.org".as_bytes().to_vec())) + ); + } + + #[test] + fn test_dns_parse_name_double_pointer() { + let buf: &[u8] = &[ + 0xd8, 0xcb, 0x8a, 0xed, 0xa1, 0x46, 0x00, 0x15, /* 0: .....F.. */ + 0x17, 0x0d, 0x06, 0xf7, 0x08, 0x00, 0x45, 0x00, /* 8: ......E. */ + 0x00, 0x66, 0x5e, 0x20, 0x40, 0x00, 0x40, 0x11, /* 16: .f^ @.@. */ + 0xc6, 0x3b, 0x0a, 0x10, 0x01, 0x01, 0x0a, 0x10, /* 24: .;...... */ + 0x01, 0x0b, 0x00, 0x35, 0xc2, 0x21, 0x00, 0x52, /* 32: ...5.!.R */ + 0x35, 0xc5, 0x0d, 0x4f, 0x81, 0x80, 0x00, 0x01, /* 40: 5..O.... */ + 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x05, 0x62, /* 48: .......b */ + 0x6c, 0x6f, 0x63, 0x6b, 0x07, 0x64, 0x72, 0x6f, /* 56: lock.dro */ + 0x70, 0x62, 0x6f, 0x78, 0x03, 0x63, 0x6f, 0x6d, /* 64: pbox.com */ + 0x00, 0x00, 0x01, 0x00, 0x01, 0xc0, 0x0c, 0x00, /* 72: ........ */ + 0x05, 0x00, 0x01, 0x00, 0x00, 0x00, 0x09, 0x00, /* 80: ........ */ + 0x0b, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x02, /* 88: ..block. */ + 0x67, 0x31, 0xc0, 0x12, 0xc0, 0x2f, 0x00, 0x01, /* 96: g1.../.. */ + 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, /* 104: ........ */ + 0x2d, 0x3a, 0x46, 0x21, /* 112: -:F! */ + ]; + + // The start of the DNS message in the above packet. + let message: &[u8] = &buf[42..]; + + // The start of the name we want to parse, 0xc0 0x2f, a + // pointer to offset 47 in the message (or 89 in the full + // packet). + let start: &[u8] = &buf[100..]; + + let res = dns_parse_name(start, message); + assert_eq!( + res, + Ok((&start[2..], "block.g1.dropbox.com".as_bytes().to_vec())) + ); + } + + #[test] + fn test_dns_parse_request() { + // DNS request from dig-a-www.suricata-ids.org.pcap. + let pkt: &[u8] = &[ + 0x8d, 0x32, 0x01, 0x20, 0x00, 0x01, /* ...2. .. */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x00, /* ..)..... */ + 0x00, 0x00, 0x00, /* ... */ + ]; + + let res = dns_parse_request(pkt); + match res { + Ok((rem, request)) => { + // For now we have some remainder data as there is an + // additional record type we don't parse yet. + assert!(!rem.is_empty()); + + assert_eq!( + request.header, + DNSHeader { + tx_id: 0x8d32, + flags: 0x0120, + questions: 1, + answer_rr: 0, + authority_rr: 0, + additional_rr: 1, + } + ); + + assert_eq!(request.queries.len(), 1); + + let query = &request.queries[0]; + assert_eq!(query.name, "www.suricata-ids.org".as_bytes().to_vec()); + assert_eq!(query.rrtype, 1); + assert_eq!(query.rrclass, 1); + } + _ => { + assert!(false); + } + } + } + + /// Parse a DNS response. + fn dns_parse_response(message: &[u8]) -> IResult<&[u8], DNSResponse> { + let i = message; + let (i, header) = dns_parse_header(i)?; + dns_parse_response_body(i, message, header) + } + + #[test] + fn test_dns_parse_response() { + // DNS response from dig-a-www.suricata-ids.org.pcap. + let pkt: &[u8] = &[ + 0x8d, 0x32, 0x81, 0xa0, 0x00, 0x01, /* ...2.... */ + 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x77, /* .......w */ + 0x77, 0x77, 0x0c, 0x73, 0x75, 0x72, 0x69, 0x63, /* ww.suric */ + 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, 0x73, 0x03, /* ata-ids. */ + 0x6f, 0x72, 0x67, 0x00, 0x00, 0x01, 0x00, 0x01, /* org..... */ + 0xc0, 0x0c, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, /* ........ */ + 0x0d, 0xd8, 0x00, 0x12, 0x0c, 0x73, 0x75, 0x72, /* .....sur */ + 0x69, 0x63, 0x61, 0x74, 0x61, 0x2d, 0x69, 0x64, /* icata-id */ + 0x73, 0x03, 0x6f, 0x72, 0x67, 0x00, 0xc0, 0x32, /* s.org..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x18, 0xc0, 0x32, /* ....N..2 */ + 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4, /* ........ */ + 0x00, 0x04, 0xc0, 0x00, 0x4e, 0x19, /* ....N. */ + ]; + + let res = dns_parse_response(pkt); + match res { + Ok((rem, response)) => { + // The response should be full parsed. + assert_eq!(rem.len(), 0); + + assert_eq!( + response.header, + DNSHeader { + tx_id: 0x8d32, + flags: 0x81a0, + questions: 1, + answer_rr: 3, + authority_rr: 0, + additional_rr: 0, + } + ); + + assert_eq!(response.answers.len(), 3); + + let answer1 = &response.answers[0]; + assert_eq!(answer1.name, "www.suricata-ids.org".as_bytes().to_vec()); + assert_eq!(answer1.rrtype, 5); + assert_eq!(answer1.rrclass, 1); + assert_eq!(answer1.ttl, 3544); + assert_eq!( + answer1.data, + DNSRData::CNAME("suricata-ids.org".as_bytes().to_vec()) + ); + + let answer2 = &response.answers[1]; + assert_eq!( + answer2, + &DNSAnswerEntry { + name: "suricata-ids.org".as_bytes().to_vec(), + rrtype: 1, + rrclass: 1, + ttl: 244, + data: DNSRData::A([192, 0, 78, 24].to_vec()), + } + ); + + let answer3 = &response.answers[2]; + assert_eq!( + answer3, + &DNSAnswerEntry { + name: "suricata-ids.org".as_bytes().to_vec(), + rrtype: 1, + rrclass: 1, + ttl: 244, + data: DNSRData::A([192, 0, 78, 25].to_vec()), + } + ) + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_dns_parse_response_nxdomain_soa() { + // DNS response with an SOA authority record from + // dns-udp-nxdomain-soa.pcap. + let pkt: &[u8] = &[ + 0x82, 0x95, 0x81, 0x83, 0x00, 0x01, /* j....... */ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x03, 0x64, /* .......d */ + 0x6e, 0x65, 0x04, 0x6f, 0x69, 0x73, 0x66, 0x03, /* ne.oisf. */ + 0x6e, 0x65, 0x74, 0x00, 0x00, 0x01, 0x00, 0x01, /* net..... */ + 0xc0, 0x10, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, /* ........ */ + 0x03, 0x83, 0x00, 0x45, 0x06, 0x6e, 0x73, 0x2d, /* ...E.ns- */ + 0x31, 0x31, 0x30, 0x09, 0x61, 0x77, 0x73, 0x64, /* 110.awsd */ + 0x6e, 0x73, 0x2d, 0x31, 0x33, 0x03, 0x63, 0x6f, /* ns-13.co */ + 0x6d, 0x00, 0x11, 0x61, 0x77, 0x73, 0x64, 0x6e, /* m..awsdn */ + 0x73, 0x2d, 0x68, 0x6f, 0x73, 0x74, 0x6d, 0x61, /* s-hostma */ + 0x73, 0x74, 0x65, 0x72, 0x06, 0x61, 0x6d, 0x61, /* ster.ama */ + 0x7a, 0x6f, 0x6e, 0xc0, 0x3b, 0x00, 0x00, 0x00, /* zon.;... */ + 0x01, 0x00, 0x00, 0x1c, 0x20, 0x00, 0x00, 0x03, /* .... ... */ + 0x84, 0x00, 0x12, 0x75, 0x00, 0x00, 0x01, 0x51, /* ...u...Q */ + 0x80, 0x00, 0x00, 0x29, 0x02, 0x00, 0x00, 0x00, /* ...).... */ + 0x00, 0x00, 0x00, 0x00, /* .... */ + ]; + + let res = dns_parse_response(pkt); + match res { + Ok((rem, response)) => { + // For now we have some remainder data as there is an + // additional record type we don't parse yet. + assert!(!rem.is_empty()); + + assert_eq!( + response.header, + DNSHeader { + tx_id: 0x8295, + flags: 0x8183, + questions: 1, + answer_rr: 0, + authority_rr: 1, + additional_rr: 1, + } + ); + + assert_eq!(response.authorities.len(), 1); + + let authority = &response.authorities[0]; + assert_eq!(authority.name, "oisf.net".as_bytes().to_vec()); + assert_eq!(authority.rrtype, 6); + assert_eq!(authority.rrclass, 1); + assert_eq!(authority.ttl, 899); + assert_eq!( + authority.data, + DNSRData::SOA(DNSRDataSOA { + mname: "ns-110.awsdns-13.com".as_bytes().to_vec(), + rname: "awsdns-hostmaster.amazon.com".as_bytes().to_vec(), + serial: 1, + refresh: 7200, + retry: 900, + expire: 1209600, + minimum: 86400, + }) + ); + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_dns_parse_response_null() { + // DNS response with a NULL record from + // https://redmine.openinfosecfoundation.org/attachments/2062 + + let pkt: &[u8] = &[ + 0x12, 0xb0, 0x84, 0x00, 0x00, 0x01, 0x00, 0x01, /* ........ */ + 0x00, 0x00, 0x00, 0x00, 0x0b, 0x76, 0x61, 0x61, /* .....vaa */ + 0x61, 0x61, 0x6b, 0x61, 0x72, 0x64, 0x6c, 0x69, /* aakardli */ + 0x06, 0x70, 0x69, 0x72, 0x61, 0x74, 0x65, 0x03, /* .pirate. */ + 0x73, 0x65, 0x61, 0x00, 0x00, 0x0a, 0x00, 0x01, /* sea..... */ + 0xc0, 0x0c, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x00, /* ........ */ + 0x00, 0x00, 0x00, 0x09, 0x56, 0x41, 0x43, 0x4b, /* ....VACK */ + 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ + ]; + + let res = dns_parse_response(pkt); + match res { + Ok((rem, response)) => { + // The response should be fully parsed. + assert_eq!(rem.len(), 0); + + assert_eq!( + response.header, + DNSHeader { + tx_id: 0x12b0, + flags: 0x8400, + questions: 1, + answer_rr: 1, + authority_rr: 0, + additional_rr: 0, + } + ); + + assert_eq!(response.queries.len(), 1); + let query = &response.queries[0]; + assert_eq!(query.name, "vaaaakardli.pirate.sea".as_bytes().to_vec()); + assert_eq!(query.rrtype, DNS_RECORD_TYPE_NULL); + assert_eq!(query.rrclass, 1); + + assert_eq!(response.answers.len(), 1); + + let answer = &response.answers[0]; + assert_eq!(answer.name, "vaaaakardli.pirate.sea".as_bytes().to_vec()); + assert_eq!(answer.rrtype, DNS_RECORD_TYPE_NULL); + assert_eq!(answer.rrclass, 1); + assert_eq!(answer.ttl, 0); + assert_eq!( + answer.data, + DNSRData::NULL(vec![ + 0x56, 0x41, 0x43, 0x4b, /* VACK */ + 0x44, 0x03, 0xc5, 0xe9, 0x01, /* D.... */ + ]) + ); + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_dns_parse_rdata_sshfp() { + // Dummy data since we don't have a pcap sample. + let data: &[u8] = &[ + // algo: DSS + 0x02, // fp_type: SHA-1 + 0x01, // fingerprint: 123456789abcdef67890123456789abcdef67890 + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, + 0x9a, 0xbc, 0xde, 0xf6, 0x78, 0x90, + ]; + + let res = dns_parse_rdata_sshfp(data); + match res { + Ok((rem, rdata)) => { + // The data should be fully parsed. + assert_eq!(rem.len(), 0); + + match rdata { + DNSRData::SSHFP(sshfp) => { + assert_eq!(sshfp.algo, 2); + assert_eq!(sshfp.fp_type, 1); + assert_eq!(sshfp.fingerprint, &data[2..]); + } + _ => { + assert!(false); + } + } + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_dns_parse_rdata_srv() { + /* ; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> _sip._udp.sip.voice.google.com SRV + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 1524 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 3 + + [...] + + ;; ANSWER SECTION: + _sip._udp.sip.voice.google.com. 300 IN SRV 10 1 5060 sip-anycast-1.voice.google.com. + _sip._udp.sip.voice.google.com. 300 IN SRV 20 1 5060 sip-anycast-2.voice.google.com. + + [...] + + ;; Query time: 72 msec + ;; MSG SIZE rcvd: 191 */ + + let pkt: &[u8] = &[ + 0xeb, 0x56, 0x81, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x04, 0x5f, + 0x73, 0x69, 0x70, 0x04, 0x5f, 0x75, 0x64, 0x70, 0x03, 0x73, 0x69, 0x70, 0x05, 0x76, + 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x21, 0x00, 0x01, 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, + 0x01, 0x13, 0x00, 0x26, 0x00, 0x14, 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, + 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, 0x73, 0x74, 0x2d, 0x32, 0x05, 0x76, 0x6f, 0x69, + 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, + 0xc0, 0x0c, 0x00, 0x21, 0x00, 0x01, 0x00, 0x00, 0x01, 0x13, 0x00, 0x26, 0x00, 0x0a, + 0x00, 0x01, 0x13, 0xc4, 0x0d, 0x73, 0x69, 0x70, 0x2d, 0x61, 0x6e, 0x79, 0x63, 0x61, + 0x73, 0x74, 0x2d, 0x31, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x06, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x03, 0x63, 0x6f, 0x6d, 0x00, + ]; + + let res = dns_parse_response(pkt); + match res { + Ok((rem, response)) => { + // The data should be fully parsed. + assert_eq!(rem.len(), 0); + + assert_eq!(response.answers.len(), 2); + + let answer1 = &response.answers[0]; + match &answer1.data { + DNSRData::SRV(srv) => { + assert_eq!(srv.priority, 20); + assert_eq!(srv.weight, 1); + assert_eq!(srv.port, 5060); + assert_eq!( + srv.target, + "sip-anycast-2.voice.google.com".as_bytes().to_vec() + ); + } + _ => { + assert!(false); + } + } + let answer2 = &response.answers[1]; + match &answer2.data { + DNSRData::SRV(srv) => { + assert_eq!(srv.priority, 10); + assert_eq!(srv.weight, 1); + assert_eq!(srv.port, 5060); + assert_eq!( + srv.target, + "sip-anycast-1.voice.google.com".as_bytes().to_vec() + ); + } + _ => { + assert!(false); + } + } + } + _ => { + assert!(false); + } + } + } +} diff --git a/rust/src/feature.rs b/rust/src/feature.rs new file mode 100644 index 0000000..abd0966 --- /dev/null +++ b/rust/src/feature.rs @@ -0,0 +1,60 @@ +/* Copyright (C) 2023 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. + */ + +//! Rust bindings to the "feature" API. +//! +//! As this feature module is a binding to a Suricata C module it is +//! not available to Rust unit tests. Instead when running Rust unit +//! tests and "mock" version is provided that will return true for any +//! feature starting with "true" and false for any other feature name. + +#[cfg(test)] +mod mock { + /// Check for a feature returning true if found. + /// + /// This a "mock" variant of `requires` that will return true for + /// any feature starting with string `true`, and false for + /// anything else. + pub fn requires(feature: &str) -> bool { + return feature.starts_with("true"); + } +} + +#[cfg(not(test))] +mod real { + use std::ffi::CString; + use std::os::raw::c_char; + + extern "C" { + fn RequiresFeature(feature: *const c_char) -> bool; + } + + /// Check for a feature returning true if found. + pub fn requires(feature: &str) -> bool { + if let Ok(feature) = CString::new(feature) { + unsafe { RequiresFeature(feature.as_ptr()) } + } else { + false + } + } +} + +#[cfg(not(test))] +pub use real::*; + +#[cfg(test)] +pub use mock::*; diff --git a/rust/src/ffi/base64.rs b/rust/src/ffi/base64.rs new file mode 100644 index 0000000..ea72a34 --- /dev/null +++ b/rust/src/ffi/base64.rs @@ -0,0 +1,62 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::os::raw::c_uchar; +use libc::c_ulong; + +#[repr(C)] +#[allow(non_camel_case_types)] +pub enum Base64ReturnCode { + SC_BASE64_OK = 0, + SC_BASE64_INVALID_ARG, + SC_BASE64_OVERFLOW, +} + +/// Base64 encode a buffer. +/// +/// This method exposes the Rust base64 encoder to C and should not be called from +/// Rust code. +/// +/// The output parameter must be an allocated buffer of at least the size returned +/// from Base64EncodeBufferSize for the input_len, and this length must be provided +/// in the output_len variable. +#[no_mangle] +pub unsafe extern "C" fn Base64Encode( + input: *const u8, input_len: c_ulong, output: *mut c_uchar, output_len: *mut c_ulong, +) -> Base64ReturnCode { + if input.is_null() || output.is_null() || output_len.is_null() { + return Base64ReturnCode::SC_BASE64_INVALID_ARG; + } + let input = std::slice::from_raw_parts(input, input_len as usize); + let encoded = base64::encode(input); + if encoded.len() + 1 > *output_len as usize { + return Base64ReturnCode::SC_BASE64_OVERFLOW; + } + let output = std::slice::from_raw_parts_mut(&mut *output, *output_len as usize); + output[0..encoded.len()].copy_from_slice(encoded.as_bytes()); + output[encoded.len()] = 0; + *output_len = encoded.len() as c_ulong; + Base64ReturnCode::SC_BASE64_OK +} + +/// Ratio of output bytes to input bytes for Base64 Encoding is 4:3, hence the +/// required output bytes are 4 * ceil(input_len / 3) and an additional byte for +/// storing the NULL pointer. +#[no_mangle] +pub extern "C" fn Base64EncodeBufferSize(len: c_ulong) -> c_ulong { + (4 * ((len) + 2) / 3) + 1 +} diff --git a/rust/src/ffi/hashing.rs b/rust/src/ffi/hashing.rs new file mode 100644 index 0000000..59c7c9d --- /dev/null +++ b/rust/src/ffi/hashing.rs @@ -0,0 +1,252 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use digest::{Digest, Update}; +use md5::Md5; +use sha1::Sha1; +use sha2::Sha256; +use std::os::raw::c_char; + +pub const SC_SHA1_LEN: usize = 20; +pub const SC_SHA256_LEN: usize = 32; + +// Length of hex digests without trailing NUL. +pub const SC_MD5_HEX_LEN: usize = 32; +pub const SC_SHA256_HEX_LEN: usize = 64; + +// Wrap the Rust Sha256 in a new type named SCSha256 to give this type +// the "SC" prefix. The one drawback is we must access the actual context +// with .0. +pub struct SCSha256(Sha256); + +#[no_mangle] +pub extern "C" fn SCSha256New() -> *mut SCSha256 { + let hasher = Box::new(SCSha256(Sha256::new())); + Box::into_raw(hasher) +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha256Update(hasher: &mut SCSha256, bytes: *const u8, len: u32) { + update(&mut hasher.0, bytes, len); +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha256Finalize(hasher: &mut SCSha256, out: *mut u8, len: u32) { + let hasher: Box<SCSha256> = Box::from_raw(hasher); + finalize(hasher.0, out, len); +} + +/// C function to finalize the Sha256 hasher to a hex string. +/// +/// Notes: +/// - There is probably room for optimization here, by iterating the result and writing +/// the output directly to the output buffer. +/// +/// But even given the notes, this appears to be faster than the equivalent that we +/// did in C using NSS. +#[no_mangle] +pub unsafe extern "C" fn SCSha256FinalizeToHex(hasher: &mut SCSha256, out: *mut c_char, len: u32) { + let hasher: Box<SCSha256> = Box::from_raw(hasher); + let result = hasher.0.finalize(); + let hex = format!("{:x}", &result); + crate::ffi::strings::copy_to_c_char(hex, out, len as usize); +} + +/// Free an unfinalized Sha256 context. +#[no_mangle] +pub unsafe extern "C" fn SCSha256Free(hasher: &mut SCSha256) { + // Drop. + let _: Box<SCSha256> = Box::from_raw(hasher); +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha256HashBuffer( + buf: *const u8, buf_len: u32, out: *mut u8, len: u32, +) -> bool { + if len as usize != SC_SHA256_LEN { + return false; + } + let data = std::slice::from_raw_parts(buf, buf_len as usize); + let output = std::slice::from_raw_parts_mut(out, len as usize); + let hash = Sha256::new().chain(data).finalize(); + output.copy_from_slice(&hash); + return true; +} + +// Start of SHA1 C bindings. + +pub struct SCSha1(Sha1); + +#[no_mangle] +pub extern "C" fn SCSha1New() -> *mut SCSha1 { + let hasher = Box::new(SCSha1(Sha1::new())); + Box::into_raw(hasher) +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha1Update(hasher: &mut SCSha1, bytes: *const u8, len: u32) { + update(&mut hasher.0, bytes, len); +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha1Finalize(hasher: &mut SCSha1, out: *mut u8, len: u32) { + let hasher: Box<SCSha1> = Box::from_raw(hasher); + finalize(hasher.0, out, len); +} + +/// Free an unfinalized Sha1 context. +#[no_mangle] +pub unsafe extern "C" fn SCSha1Free(hasher: &mut SCSha1) { + // Drop. + let _: Box<SCSha1> = Box::from_raw(hasher); +} + +#[no_mangle] +pub unsafe extern "C" fn SCSha1HashBuffer( + buf: *const u8, buf_len: u32, out: *mut u8, len: u32, +) -> bool { + if len as usize != SC_SHA1_LEN { + return false; + } + let data = std::slice::from_raw_parts(buf, buf_len as usize); + let output = std::slice::from_raw_parts_mut(out, len as usize); + let hash = Sha1::new().chain(data).finalize(); + output.copy_from_slice(&hash); + return true; +} + +// Start of MD5 C bindings. + +pub struct SCMd5(Md5); + +#[no_mangle] +pub extern "C" fn SCMd5New() -> *mut SCMd5 { + let hasher = Box::new(SCMd5(Md5::new())); + Box::into_raw(hasher) +} + +#[no_mangle] +pub unsafe extern "C" fn SCMd5Update(hasher: &mut SCMd5, bytes: *const u8, len: u32) { + update(&mut hasher.0, bytes, len); +} + +/// Finalize the MD5 hash placing the digest in the provided out buffer. +/// +/// This function consumes the SCMd5 hash context. +#[no_mangle] +pub unsafe extern "C" fn SCMd5Finalize(hasher: &mut SCMd5, out: *mut u8, len: u32) { + let hasher: Box<SCMd5> = Box::from_raw(hasher); + finalize(hasher.0, out, len); +} + +/// Finalize MD5 context to a hex string. +/// +/// Consumes the hash context and cannot be re-used. +#[no_mangle] +pub unsafe extern "C" fn SCMd5FinalizeToHex(hasher: &mut SCMd5, out: *mut c_char, len: u32) { + let hasher: Box<SCMd5> = Box::from_raw(hasher); + let result = hasher.0.finalize(); + let hex = format!("{:x}", &result); + crate::ffi::strings::copy_to_c_char(hex, out, len as usize); +} + +/// Free an unfinalized Sha1 context. +#[no_mangle] +pub unsafe extern "C" fn SCMd5Free(hasher: &mut SCMd5) { + // Drop. + let _: Box<SCMd5> = Box::from_raw(hasher); +} + +#[no_mangle] +pub unsafe extern "C" fn SCMd5HashBuffer(buf: *const u8, buf_len: u32, out: *mut u8, len: u32) { + let data = std::slice::from_raw_parts(buf, buf_len as usize); + let output = std::slice::from_raw_parts_mut(out, len as usize); + let hash = Md5::new().chain(data).finalize(); + output.copy_from_slice(&hash); +} + +/// C binding for a function to MD5 hash a single buffer to a hex string. +#[no_mangle] +pub unsafe extern "C" fn SCMd5HashBufferToHex( + buf: *const u8, buf_len: u32, out: *mut c_char, len: u32, +) { + let data = std::slice::from_raw_parts(buf, buf_len as usize); + let hash = Md5::new().chain(data).finalize(); + let hex = format!("{:x}", &hash); + crate::ffi::strings::copy_to_c_char(hex, out, len as usize); +} + +// Functions that are generic over Digest. For the most part the C bindings are +// just wrappers around these. + +unsafe fn update<D: Digest>(digest: &mut D, bytes: *const u8, len: u32) { + let data = std::slice::from_raw_parts(bytes, len as usize); + digest.update(data); +} + +unsafe fn finalize<D: Digest>(digest: D, out: *mut u8, len: u32) { + let result = digest.finalize(); + let output = std::slice::from_raw_parts_mut(out, len as usize); + // This will panic if the sizes differ. + output.copy_from_slice(&result); +} + +#[cfg(test)] +mod test { + use super::*; + + // A test around SCSha256 primarily to check that the output is + // correctly copied into a C string. + #[test] + fn test_sha256() { + unsafe { + let hasher = SCSha256New(); + assert!(!hasher.is_null()); + let hasher = &mut *hasher as &mut SCSha256; + let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41]; + SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCSha256Update(hasher, bytes.as_ptr(), bytes.len() as u32); + let hex = [0_u8; SC_SHA256_HEX_LEN + 1]; + SCSha256FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_SHA256_HEX_LEN + 1) as u32); + let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap(); + assert_eq!(string, "22a48051594c1949deed7040850c1f0f8764537f5191be56732d16a54c1d8153"); + } + } + + // A test around SCSha256 primarily to check that the output is + // correctly copied into a C string. + #[test] + fn test_md5() { + unsafe { + let hasher = SCMd5New(); + assert!(!hasher.is_null()); + let hasher = &mut *hasher as &mut SCMd5; + let bytes = &[0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41]; + SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32); + SCMd5Update(hasher, bytes.as_ptr(), bytes.len() as u32); + let hex = [0_u8; SC_MD5_HEX_LEN + 1]; + SCMd5FinalizeToHex(hasher, hex.as_ptr() as *mut c_char, (SC_MD5_HEX_LEN + 1) as u32); + let string = std::ffi::CStr::from_ptr(hex.as_ptr() as *mut c_char).to_str().unwrap(); + assert_eq!(string, "5216ddcc58e8dade5256075e77f642da"); + } + } + +} diff --git a/rust/src/ffi/mod.rs b/rust/src/ffi/mod.rs new file mode 100644 index 0000000..e97e6c9 --- /dev/null +++ b/rust/src/ffi/mod.rs @@ -0,0 +1,22 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Module that exposes C bindings to the Suricata Rust library. + +pub mod hashing; +pub mod base64; +pub mod strings; diff --git a/rust/src/ffi/strings.rs b/rust/src/ffi/strings.rs new file mode 100644 index 0000000..1374cb2 --- /dev/null +++ b/rust/src/ffi/strings.rs @@ -0,0 +1,84 @@ +/* Copyright (C) 2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::ffi::CString; +use std::os::raw::c_char; + +/// FFI utility function to copy a Rust string to a C string buffer. +/// +/// Return true on success. On error, false will be returned. +/// +/// An error will be returned if the provided string cannot be +/// converted to a C string (for example, it contains NULs), or if the +/// provided buffer is not large enough. +/// +/// # Safety +/// +/// Unsafe as this depends on the caller providing valid buf and size +/// parameters. +pub unsafe fn copy_to_c_char(src: String, buf: *mut c_char, size: usize) -> bool { + if let Ok(src) = CString::new(src) { + let src = src.as_bytes_with_nul(); + if size >= src.len() { + let buf = std::slice::from_raw_parts_mut(buf as *mut u8, size); + buf[0..src.len()].copy_from_slice(src); + return true; + } + } + false +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_copy_to_c_char() { + unsafe { + const INPUT: &str = "1234567890"; + let buf = [0_i8; INPUT.len() + 1]; + assert!(copy_to_c_char( + INPUT.to_string(), + buf.as_ptr() as *mut c_char, + buf.len() + )); + // Note that while CStr::from_ptr is documented to take a + // *const i8, on Arm/Arm64 it actually takes a *const + // u8. So cast it c_char which is an alias for the correct + // type depending on the arch. + let output = std::ffi::CStr::from_ptr(buf.as_ptr() as *const c_char) + .to_str() + .unwrap(); + assert_eq!(INPUT, output); + }; + } + + // Test `copy_to_c_char` with too short of an output buffer to + // make sure false is returned. + #[test] + fn test_copy_to_c_char_short_output() { + unsafe { + const INPUT: &str = "1234567890"; + let buf = [0_i8; INPUT.len()]; + assert!(!copy_to_c_char( + INPUT.to_string(), + buf.as_ptr() as *mut c_char, + buf.len() + )); + }; + } +} diff --git a/rust/src/filecontainer.rs b/rust/src/filecontainer.rs new file mode 100644 index 0000000..3a8bde5 --- /dev/null +++ b/rust/src/filecontainer.rs @@ -0,0 +1,105 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! This module handles file container operations (open, append, close). + +use std::ptr; +use std::os::raw::{c_void}; + +use crate::core::*; + +// Defined in util-file.h +extern { + pub fn FileFlowFlagsToFlags(flow_file_flags: u16, flags: u8) -> u16; +} + +#[repr(C)] +#[derive(Debug)] +pub struct FileContainer { + head: * mut c_void, + tail: * mut c_void, +} + +impl Default for FileContainer { + fn default() -> Self { Self { + head: ptr::null_mut(), + tail: ptr::null_mut(), + }} +} + +impl FileContainer { + pub fn free(&mut self, cfg: &'static SuricataFileContext) { + SCLogDebug!("freeing self"); + if let Some(c) = unsafe {SC} { + (c.FileContainerRecycle)(self, cfg.files_sbcfg); + } + } + + pub fn file_open(&mut self, cfg: &'static SuricataFileContext, track_id: u32, name: &[u8], flags: u16) -> i32 { + match unsafe {SC} { + None => panic!("BUG no suricata_config"), + Some(c) => { + SCLogDebug!("FILE {:p} OPEN flags {:04X}", &self, flags); + + let res = (c.FileOpenFile)(self, cfg.files_sbcfg, track_id, + name.as_ptr(), name.len() as u16, + ptr::null(), 0u32, flags); + res + } + } + } + + pub fn file_append(&mut self, cfg: &'static SuricataFileContext, track_id: &u32, data: &[u8], is_gap: bool) -> i32 { + SCLogDebug!("FILECONTAINER: append {}", data.len()); + if data.is_empty() { + return 0 + } + match unsafe {SC} { + None => panic!("BUG no suricata_config"), + Some(c) => { + let res = match is_gap { + false => { + SCLogDebug!("appending file data"); + let r = (c.FileAppendData)(self, cfg.files_sbcfg, *track_id, + data.as_ptr(), data.len() as u32); + r + }, + true => { + SCLogDebug!("appending GAP"); + let r = (c.FileAppendGAP)(self, cfg.files_sbcfg, *track_id, + data.as_ptr(), data.len() as u32); + r + }, + }; + res + } + } + } + + pub fn file_close(&mut self, cfg: &'static SuricataFileContext, track_id: &u32, flags: u16) -> i32 { + SCLogDebug!("FILECONTAINER: CLOSEing"); + + match unsafe {SC} { + None => panic!("BUG no suricata_config"), + Some(c) => { + let res = (c.FileCloseFile)(self, cfg.files_sbcfg, *track_id, ptr::null(), 0u32, flags); + res + } + } + + } +} diff --git a/rust/src/filetracker.rs b/rust/src/filetracker.rs new file mode 100644 index 0000000..3ae65ee --- /dev/null +++ b/rust/src/filetracker.rs @@ -0,0 +1,350 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Gap handling and Chunk-based file transfer tracker module. +//! +//! GAP handling. If a data gap is encountered, the file is truncated +//! and new data is no longer pushed down to the lower level APIs. +//! The tracker does continue to follow the file +// +//! Tracks chunk based file transfers. Chunks may be transferred out +//! of order, but cannot be transferred in parallel. So only one +//! chunk at a time. +//! +//! Author: Victor Julien <victor@inliniac.net> + +use crate::core::*; +use std::collections::HashMap; +use std::collections::hash_map::Entry::{Occupied, Vacant}; +use crate::filecontainer::*; + +#[derive(Debug)] +struct FileChunk { + contains_gap: bool, + chunk: Vec<u8>, +} + +impl FileChunk { + pub fn new(size: u32) -> FileChunk { + FileChunk { + contains_gap: false, + chunk: Vec::with_capacity(size as usize), + } + } +} + +#[derive(Debug)] +#[derive(Default)] +pub struct FileTransferTracker { + pub tracked: u64, + cur_ooo: u64, // how many bytes do we have queued from ooo chunks + track_id: u32, + chunk_left: u32, + + pub file: FileContainer, + pub file_flags: u16, + + pub tx_id: u64, + + fill_bytes: u8, + pub file_open: bool, + file_closed: bool, + chunk_is_last: bool, + chunk_is_ooo: bool, + file_is_truncated: bool, + + chunks: HashMap<u64, FileChunk>, + cur_ooo_chunk_offset: u64, + + in_flight: u64, +} + +impl FileTransferTracker { + pub fn new() -> FileTransferTracker { + FileTransferTracker { + chunks:HashMap::new(), + ..Default::default() + } + } + + pub fn is_done(&self) -> bool { + !self.file_open + } + + pub fn is_initialized(&self) -> bool { + return self.file_open || self.file_is_truncated || self.file_closed; + } + + fn open(&mut self, config: &'static SuricataFileContext, name: &[u8]) -> i32 + { + let r = self.file.file_open(config, self.track_id, name, self.file_flags); + if r == 0 { + self.file_open = true; + } + r + } + + pub fn close(&mut self, config: &'static SuricataFileContext) + { + if !self.file_is_truncated { + SCLogDebug!("closing file with id {}", self.track_id); + self.file.file_close(config, &self.track_id, self.file_flags); + } + self.file_open = false; + self.file_closed = true; + self.tracked = 0; + } + + pub fn trunc (&mut self, config: &'static SuricataFileContext) + { + if self.file_is_truncated || !self.file_open { + return; + } + let myflags = self.file_flags | 1; // TODO util-file.c::FILE_TRUNCATED + self.file.file_close(config, &self.track_id, myflags); + SCLogDebug!("truncated file"); + self.file_is_truncated = true; + self.chunks.clear(); + self.in_flight = 0; + self.cur_ooo = 0; + } + + pub fn new_chunk(&mut self, config: &'static SuricataFileContext, + name: &[u8], data: &[u8], chunk_offset: u64, chunk_size: u32, + fill_bytes: u8, is_last: bool, xid: &u32) -> u32 + { + if self.chunk_left != 0 || self.fill_bytes != 0 { + SCLogDebug!("current chunk incomplete: truncating"); + self.trunc(config); + } + + SCLogDebug!("NEW CHUNK: chunk_size {} fill_bytes {}", chunk_size, fill_bytes); + + // for now assume that is_last means its really the last chunk + // so no out of order chunks coming after. This means that if + // the last chunk is out or order, we've missed chunks before. + if chunk_offset != self.tracked { + SCLogDebug!("NEW CHUNK IS OOO: expected {}, got {}", self.tracked, chunk_offset); + if is_last { + SCLogDebug!("last chunk is out of order, this means we missed data before"); + self.trunc(config); + } + self.chunk_is_ooo = true; + self.cur_ooo_chunk_offset = chunk_offset; + } + + self.chunk_left = chunk_size; + self.fill_bytes = fill_bytes; + self.chunk_is_last = is_last; + + if self.file_is_truncated || self.file_closed { + return 0; + } + if !self.file_open { + SCLogDebug!("NEW CHUNK: FILE OPEN"); + self.track_id = *xid; + self.open(config, name); + } + + if self.file_open { + let res = self.update(config, data, 0); + SCLogDebug!("NEW CHUNK: update res {:?}", res); + return res; + } + + 0 + } + + /// update the file tracker + /// If gap_size > 0 'data' should not be used. + /// return how much we consumed of data + pub fn update(&mut self, config: &'static SuricataFileContext, data: &[u8], gap_size: u32) -> u32 + { + if self.file_is_truncated { + let consumed = std::cmp::min(data.len() as u32, self.chunk_left); + self.chunk_left = self.chunk_left.saturating_sub(data.len() as u32); + return consumed; + } + let mut consumed = 0_usize; + let is_gap = gap_size > 0; + if is_gap || gap_size > 0 { + SCLogDebug!("is_gap {} size {} ooo? {}", is_gap, gap_size, self.chunk_is_ooo); + } + + if self.chunk_left == 0 && self.fill_bytes == 0 { + //SCLogDebug!("UPDATE: nothing to do"); + if self.chunk_is_last { + SCLogDebug!("last empty chunk, closing"); + self.close(config); + self.chunk_is_last = false; + } + return 0 + } else if self.chunk_left == 0 { + SCLogDebug!("FILL BYTES {} from prev run", self.fill_bytes); + if data.len() >= self.fill_bytes as usize { + consumed += self.fill_bytes as usize; + self.fill_bytes = 0; + SCLogDebug!("CHUNK(pre) fill bytes now 0"); + } else { + consumed += data.len(); + self.fill_bytes -= data.len() as u8; + SCLogDebug!("CHUNK(pre) fill bytes now still {}", self.fill_bytes); + } + SCLogDebug!("FILL BYTES: returning {}", consumed); + return consumed as u32 + } + SCLogDebug!("UPDATE: data {} chunk_left {}", data.len(), self.chunk_left); + + if self.chunk_left > 0 { + if self.chunk_left <= data.len() as u32 { + let d = &data[0..self.chunk_left as usize]; + + if !self.chunk_is_ooo { + let res = self.file.file_append(config, &self.track_id, d, is_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + + self.tracked += self.chunk_left as u64; + } else { + SCLogDebug!("UPDATE: appending data {} to ooo chunk at offset {}/{}", + d.len(), self.cur_ooo_chunk_offset, self.tracked); + let c = match self.chunks.entry(self.cur_ooo_chunk_offset) { + Vacant(entry) => { + entry.insert(FileChunk::new(self.chunk_left)) + }, + Occupied(entry) => entry.into_mut(), + }; + self.cur_ooo += d.len() as u64; + c.contains_gap |= is_gap; + c.chunk.extend(d); + + self.in_flight += d.len() as u64; + SCLogDebug!("{:p} in_flight {}", self, self.in_flight); + } + + consumed += self.chunk_left as usize; + if self.fill_bytes > 0 { + let extra = data.len() - self.chunk_left as usize; + if extra >= self.fill_bytes as usize { + consumed += self.fill_bytes as usize; + self.fill_bytes = 0; + SCLogDebug!("CHUNK(post) fill bytes now 0"); + } else { + consumed += extra; + self.fill_bytes -= extra as u8; + SCLogDebug!("CHUNK(post) fill bytes now still {}", self.fill_bytes); + } + self.chunk_left = 0; + } else { + self.chunk_left = 0; + + if !self.chunk_is_ooo { + loop { + let _offset = self.tracked; + match self.chunks.remove(&self.tracked) { + Some(c) => { + self.in_flight -= c.chunk.len() as u64; + + let res = self.file.file_append(config, &self.track_id, &c.chunk, c.contains_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + + self.tracked += c.chunk.len() as u64; + self.cur_ooo -= c.chunk.len() as u64; + + SCLogDebug!("STORED OOO CHUNK at offset {}, tracked now {}, stored len {}", _offset, self.tracked, c.chunk.len()); + }, + _ => { + SCLogDebug!("NO STORED CHUNK found at _offset {}", self.tracked); + break; + }, + }; + } + } else { + SCLogDebug!("UPDATE: complete ooo chunk. Offset {}", self.cur_ooo_chunk_offset); + + self.chunk_is_ooo = false; + self.cur_ooo_chunk_offset = 0; + } + } + if self.chunk_is_last { + SCLogDebug!("last chunk, closing"); + self.close(config); + self.chunk_is_last = false; + } else { + SCLogDebug!("NOT last chunk, keep going"); + } + + } else { + if !self.chunk_is_ooo { + let res = self.file.file_append(config, &self.track_id, data, is_gap); + match res { + 0 => { }, + -2 => { + self.file_is_truncated = true; + }, + _ => { + SCLogDebug!("got error so truncating file"); + self.file_is_truncated = true; + }, + } + self.tracked += data.len() as u64; + } else { + let c = match self.chunks.entry(self.cur_ooo_chunk_offset) { + Vacant(entry) => entry.insert(FileChunk::new(32768)), + Occupied(entry) => entry.into_mut(), + }; + c.chunk.extend(data); + c.contains_gap |= is_gap; + self.cur_ooo += data.len() as u64; + self.in_flight += data.len() as u64; + } + + self.chunk_left -= data.len() as u32; + consumed += data.len(); + } + } + consumed as u32 + } + + pub fn get_queued_size(&self) -> u64 { + self.cur_ooo + } + + pub fn get_inflight_size(&self) -> u64 { + self.in_flight + } + pub fn get_inflight_cnt(&self) -> usize { + self.chunks.len() + } +} diff --git a/rust/src/frames.rs b/rust/src/frames.rs new file mode 100644 index 0000000..3a45d01 --- /dev/null +++ b/rust/src/frames.rs @@ -0,0 +1,128 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Module for bindings to the Suricata C frame API. + +use crate::applayer::StreamSlice; +use crate::core::Flow; +#[cfg(not(test))] +use crate::core::STREAM_TOSERVER; +use crate::core::Direction; + +#[cfg(not(test))] +#[repr(C)] +struct CFrame { + _private: [u8; 0], +} + +// Defined in app-layer-register.h +extern { + #[cfg(not(test))] + fn AppLayerFrameNewByRelativeOffset( + flow: *const Flow, stream_slice: *const StreamSlice, frame_start_rel: u32, len: i64, + dir: i32, frame_type: u8, + ) -> *const CFrame; + fn AppLayerFrameAddEventById(flow: *const Flow, dir: i32, id: i64, event: u8); + fn AppLayerFrameSetLengthById(flow: *const Flow, dir: i32, id: i64, len: i64); + fn AppLayerFrameSetTxIdById(flow: *const Flow, dir: i32, id: i64, tx_id: u64); + #[cfg(not(test))] + fn AppLayerFrameGetId(frame: *const CFrame) -> i64; +} + +pub struct Frame { + pub id: i64, + direction: Direction, +} + +impl std::fmt::Debug for Frame { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "frame: {}, direction: {}", self.id, self.direction) + } +} + +impl Frame { + #[cfg(not(test))] + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn new( + flow: *const Flow, stream_slice: &StreamSlice, frame_start: &[u8], frame_len: i64, + frame_type: u8, + ) -> Option<Self> { + let offset = frame_start.as_ptr() as usize - stream_slice.as_slice().as_ptr() as usize; + SCLogDebug!("offset {} stream_slice.len() {} frame_start.len() {}", offset, stream_slice.len(), frame_start.len()); + let frame = unsafe { + AppLayerFrameNewByRelativeOffset( + flow, + stream_slice, + offset as u32, + frame_len, + (stream_slice.flags() & STREAM_TOSERVER == 0).into(), + frame_type, + ) + }; + let id = unsafe { AppLayerFrameGetId(frame) }; + if id > 0 { + Some(Self { + id, + direction: Direction::from(stream_slice.flags()), + }) + } else { + None + } + } + + /// A variation of `new` for use when running Rust unit tests as + /// the C functions for building a frame are not available for + /// linkage. + #[cfg(test)] + pub fn new( + _flow: *const Flow, _stream_slice: &StreamSlice, _frame_start: &[u8], _frame_len: i64, + _frame_type: u8, + ) -> Option<Self> { + None + } + + /// Conversion function to get the direction in the correct form for the + /// C frame methods which takes direction as a u32 value of 0 or 1 rather + /// than the flag value used internally by Frame. + fn direction(&self) -> i32 { + match self.direction { + Direction::ToServer => 0, + Direction::ToClient => 1, + } + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn set_len(&self, flow: *const Flow, len: i64) { + unsafe { + AppLayerFrameSetLengthById(flow, self.direction(), self.id, len); + }; + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn set_tx(&self, flow: *const Flow, tx_id: u64) { + unsafe { + AppLayerFrameSetTxIdById(flow, self.direction(), self.id, tx_id); + }; + } + + #[allow(clippy::not_unsafe_ptr_arg_deref)] + pub fn add_event(&self, flow: *const Flow, event: u8) { + unsafe { + AppLayerFrameAddEventById(flow, self.direction(), self.id, event); + }; + } +} diff --git a/rust/src/ftp/event.rs b/rust/src/ftp/event.rs new file mode 100644 index 0000000..04cc9e3 --- /dev/null +++ b/rust/src/ftp/event.rs @@ -0,0 +1,50 @@ +/* Copyright (C) 2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::core::AppLayerEventType; +use std::os::raw::{c_char, c_int}; + +#[derive(Debug, PartialEq, Eq, AppLayerEvent)] +#[repr(C)] +pub enum FtpEvent { + #[name("request_command_too_long")] + FtpEventRequestCommandTooLong, + #[name("response_command_too_long")] + FtpEventResponseCommandTooLong, +} + +/// Wrapper around the Rust generic function for get_event_info. +/// +/// # Safety +/// Unsafe as called from C. +#[no_mangle] +pub unsafe extern "C" fn ftp_get_event_info( + event_name: *const c_char, event_id: *mut c_int, event_type: *mut AppLayerEventType, +) -> c_int { + crate::applayer::get_event_info::<FtpEvent>(event_name, event_id, event_type) +} + +/// Wrapper around the Rust generic function for get_event_info_by_id. +/// +/// # Safety +/// Unsafe as called from C. +#[no_mangle] +pub unsafe extern "C" fn ftp_get_event_info_by_id( + event_id: c_int, event_name: *mut *const c_char, event_type: *mut AppLayerEventType, +) -> c_int { + crate::applayer::get_event_info_by_id::<FtpEvent>(event_id, event_name, event_type) as c_int +} diff --git a/rust/src/ftp/mod.rs b/rust/src/ftp/mod.rs new file mode 100644 index 0000000..3839c96 --- /dev/null +++ b/rust/src/ftp/mod.rs @@ -0,0 +1,242 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! FTP parser and application layer module. + +use nom7::bytes::complete::{tag, take_until}; +use nom7::character::complete::{digit1, multispace0}; +use nom7::combinator::{complete, map_res, opt, verify}; +use nom7::sequence::{delimited, tuple}; +use nom7::{Err, IResult}; +use std; +use std::str; +use std::str::FromStr; + +pub mod event; + +// We transform an integer string into a i64, ignoring surrounding whitespaces +// We look for a digit suite, and try to convert it. +// If either str::from_utf8 or FromStr::from_str fail, +// we fallback to the parens parser defined above +fn getu16(i: &[u8]) -> IResult<&[u8], u16> { + map_res( + map_res(delimited(multispace0, digit1, multispace0), str::from_utf8), + FromStr::from_str, + )(i) +} + +fn parse_u16(i: &[u8]) -> IResult<&[u8], u16> { + map_res(map_res(digit1, str::from_utf8), u16::from_str)(i) +} + +// PORT 192,168,0,13,234,10 +pub fn ftp_active_port(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("PORT")(i)?; + let (i, _) = delimited(multispace0, digit1, multispace0)(i)?; + let (i, _) = tuple(( + tag(","), + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + ))(i)?; + let (i, part1) = verify(parse_u16, |&v| v <= std::u8::MAX as u16)(i)?; + let (i, _) = tag(",")(i)?; + let (i, part2) = verify(parse_u16, |&v| v <= std::u8::MAX as u16)(i)?; + Ok((i, part1 * 256 + part2)) +} + +// 227 Entering Passive Mode (212,27,32,66,221,243). +pub fn ftp_pasv_response(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("227")(i)?; + let (i, _) = take_until("(")(i)?; + let (i, _) = tag("(")(i)?; + let (i, _) = tuple(( + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + ))(i)?; + let (i, part1) = verify(getu16, |&v| v <= std::u8::MAX as u16)(i)?; + let (i, _) = tag(",")(i)?; + let (i, part2) = verify(getu16, |&v| v <= std::u8::MAX as u16)(i)?; + // may also be completed by a final point + let (i, _) = tag(")")(i)?; + let (i, _) = opt(complete(tag(".")))(i)?; + Ok((i, part1 * 256 + part2)) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_active_port(input: *const u8, len: u32) -> u16 { + let buf = build_slice!(input, len as usize); + match ftp_active_port(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("port incomplete: '{:?}'", buf); + } + Err(_) => { + SCLogDebug!("port error on '{:?}'", buf); + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_pasv_response(input: *const u8, len: u32) -> u16 { + let buf = std::slice::from_raw_parts(input, len as usize); + match ftp_pasv_response(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("pasv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("pasv error on '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} + +// 229 Entering Extended Passive Mode (|||48758|). +pub fn ftp_epsv_response(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("229")(i)?; + let (i, _) = take_until("|||")(i)?; + let (i, _) = tag("|||")(i)?; + let (i, port) = getu16(i)?; + let (i, _) = tag("|)")(i)?; + let (i, _) = opt(complete(tag(".")))(i)?; + Ok((i, port)) +} + +// EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|41813| +pub fn ftp_active_eprt(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("EPRT")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, port) = getu16(i)?; + let (i, _) = tag("|")(i)?; + Ok((i, port)) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_active_eprt(input: *const u8, len: u32) -> u16 { + let buf = build_slice!(input, len as usize); + match ftp_active_eprt(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("eprt incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_epsv_response(input: *const u8, len: u32) -> u16 { + let buf = std::slice::from_raw_parts(input, len as usize); + match ftp_epsv_response(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pasv_response_valid() { + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,221,243).".as_bytes()); + assert_eq!(port, Ok((&b""[..], 56819))); + let port_notdot = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,221,243)".as_bytes()); + assert_eq!(port_notdot, Ok((&b""[..], 56819))); + + let port_epsv_dot = + ftp_epsv_response("229 Entering Extended Passive Mode (|||48758|).".as_bytes()); + assert_eq!(port_epsv_dot, Ok((&b""[..], 48758))); + let port_epsv_nodot = + ftp_epsv_response("229 Entering Extended Passive Mode (|||48758|)".as_bytes()); + assert_eq!(port_epsv_nodot, Ok((&b""[..], 48758))); + } + + #[test] + fn test_active_eprt_valid() { + let port = + ftp_active_eprt("EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|41813|".as_bytes()); + assert_eq!(port, Ok((&b""[..], 41813))); + } + + #[test] + fn test_active_port_valid() { + let port = ftp_active_port("PORT 192,168,0,13,234,10".as_bytes()); + assert_eq!(port, Ok((&b""[..], 59914))); + } + + // A port that is too large for a u16. + #[test] + fn test_pasv_response_too_large() { + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,257,243).".as_bytes()); + assert!(port.is_err()); + + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,255,65535).".as_bytes()); + assert!(port.is_err()); + } + + #[test] + fn test_active_eprt_too_large() { + let port = + ftp_active_eprt("EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|81813|".as_bytes()); + assert!(port.is_err()); + } + + #[test] + fn test_active_port_too_large() { + let port = ftp_active_port("PORT 212,27,32,66,257,243".as_bytes()); + assert!(port.is_err()); + + let port = ftp_active_port("PORT 212,27,32,66,255,65535".as_bytes()); + assert!(port.is_err()); + } +} diff --git a/rust/src/http2/decompression.rs b/rust/src/http2/decompression.rs new file mode 100644 index 0000000..99f8af3 --- /dev/null +++ b/rust/src/http2/decompression.rs @@ -0,0 +1,259 @@ +/* Copyright (C) 2021 Open Information Security Foundation +* +* You can copy, redistribute or modify this Program under the terms of +* the GNU General Public License version 2 as published by the Free +* Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* version 2 along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. +*/ + +use crate::core::Direction; +use brotli; +use flate2::read::{DeflateDecoder, GzDecoder}; +use std; +use std::io; +use std::io::{Cursor, Read, Write}; + +pub const HTTP2_DECOMPRESSION_CHUNK_SIZE: usize = 0x1000; // 4096 + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2ContentEncoding { + Unknown = 0, + Gzip = 1, + Br = 2, + Deflate = 3, + Unrecognized = 4, +} + +//a cursor turning EOF into blocking errors +#[derive(Debug)] +pub struct HTTP2cursor { + pub cursor: Cursor<Vec<u8>>, +} + +impl HTTP2cursor { + pub fn new() -> HTTP2cursor { + HTTP2cursor { + cursor: Cursor::new(Vec::new()), + } + } + + pub fn set_position(&mut self, pos: u64) { + return self.cursor.set_position(pos); + } + + pub fn clear(&mut self) { + self.cursor.get_mut().clear(); + self.cursor.set_position(0); + } +} + +// we need to implement this as flate2 and brotli crates +// will read from this object +impl Read for HTTP2cursor { + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + //use the cursor, except it turns eof into blocking error + let r = self.cursor.read(buf); + match r { + Err(ref err) => { + if err.kind() == io::ErrorKind::UnexpectedEof { + return Err(io::ErrorKind::WouldBlock.into()); + } + } + Ok(0) => { + //regular EOF turned into blocking error + return Err(io::ErrorKind::WouldBlock.into()); + } + Ok(_n) => {} + } + return r; + } +} + +pub enum HTTP2Decompresser { + Unassigned, + // Box because large. + Gzip(Box<GzDecoder<HTTP2cursor>>), + // Box because large. + Brotli(Box<brotli::Decompressor<HTTP2cursor>>), + // This one is not so large, at 88 bytes as of doing this, but box + // for consistency. + Deflate(Box<DeflateDecoder<HTTP2cursor>>), +} + +impl std::fmt::Debug for HTTP2Decompresser { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + HTTP2Decompresser::Unassigned => write!(f, "UNASSIGNED"), + HTTP2Decompresser::Gzip(_) => write!(f, "GZIP"), + HTTP2Decompresser::Brotli(_) => write!(f, "BROTLI"), + HTTP2Decompresser::Deflate(_) => write!(f, "DEFLATE"), + } + } +} + +#[derive(Debug)] +struct HTTP2DecoderHalf { + encoding: HTTP2ContentEncoding, + decoder: HTTP2Decompresser, +} + +pub trait GetMutCursor { + fn get_mut(&mut self) -> &mut HTTP2cursor; +} + +impl GetMutCursor for GzDecoder<HTTP2cursor> { + fn get_mut(&mut self) -> &mut HTTP2cursor { + return self.get_mut(); + } +} + +impl GetMutCursor for DeflateDecoder<HTTP2cursor> { + fn get_mut(&mut self) -> &mut HTTP2cursor { + return self.get_mut(); + } +} + +impl GetMutCursor for brotli::Decompressor<HTTP2cursor> { + fn get_mut(&mut self) -> &mut HTTP2cursor { + return self.get_mut(); + } +} + +fn http2_decompress<'a>( + decoder: &mut (impl Read + GetMutCursor), input: &'a [u8], output: &'a mut Vec<u8>, +) -> io::Result<&'a [u8]> { + match decoder.get_mut().cursor.write_all(input) { + Ok(()) => {} + Err(e) => { + return Err(e); + } + } + let mut offset = 0; + decoder.get_mut().set_position(0); + output.resize(HTTP2_DECOMPRESSION_CHUNK_SIZE, 0); + loop { + match decoder.read(&mut output[offset..]) { + Ok(0) => { + break; + } + Ok(n) => { + offset += n; + if offset == output.len() { + output.resize(output.len() + HTTP2_DECOMPRESSION_CHUNK_SIZE, 0); + } + } + Err(e) => { + if e.kind() == io::ErrorKind::WouldBlock { + break; + } + return Err(e); + } + } + } + //brotli does not consume all input if it reaches some end + decoder.get_mut().clear(); + return Ok(&output[..offset]); +} + +impl HTTP2DecoderHalf { + pub fn new() -> HTTP2DecoderHalf { + HTTP2DecoderHalf { + encoding: HTTP2ContentEncoding::Unknown, + decoder: HTTP2Decompresser::Unassigned, + } + } + + pub fn http2_encoding_fromvec(&mut self, input: &[u8]) { + //use first encoding... + if self.encoding == HTTP2ContentEncoding::Unknown { + if input == b"gzip" { + self.encoding = HTTP2ContentEncoding::Gzip; + self.decoder = HTTP2Decompresser::Gzip(Box::new(GzDecoder::new(HTTP2cursor::new()))); + } else if input == b"deflate" { + self.encoding = HTTP2ContentEncoding::Deflate; + self.decoder = HTTP2Decompresser::Deflate(Box::new(DeflateDecoder::new(HTTP2cursor::new()))); + } else if input == b"br" { + self.encoding = HTTP2ContentEncoding::Br; + self.decoder = HTTP2Decompresser::Brotli(Box::new(brotli::Decompressor::new( + HTTP2cursor::new(), + HTTP2_DECOMPRESSION_CHUNK_SIZE, + ))); + } else { + self.encoding = HTTP2ContentEncoding::Unrecognized; + } + } + } + + pub fn decompress<'a>( + &mut self, input: &'a [u8], output: &'a mut Vec<u8>, + ) -> io::Result<&'a [u8]> { + match self.decoder { + HTTP2Decompresser::Gzip(ref mut gzip_decoder) => { + let r = http2_decompress(&mut *gzip_decoder.as_mut(), input, output); + if r.is_err() { + self.decoder = HTTP2Decompresser::Unassigned; + } + return r; + } + HTTP2Decompresser::Brotli(ref mut br_decoder) => { + let r = http2_decompress(&mut *br_decoder.as_mut(), input, output); + if r.is_err() { + self.decoder = HTTP2Decompresser::Unassigned; + } + return r; + } + HTTP2Decompresser::Deflate(ref mut df_decoder) => { + let r = http2_decompress(&mut *df_decoder.as_mut(), input, output); + if r.is_err() { + self.decoder = HTTP2Decompresser::Unassigned; + } + return r; + } + _ => {} + } + return Ok(input); + } +} + +#[derive(Debug)] +pub struct HTTP2Decoder { + decoder_tc: HTTP2DecoderHalf, + decoder_ts: HTTP2DecoderHalf, +} + +impl HTTP2Decoder { + pub fn new() -> HTTP2Decoder { + HTTP2Decoder { + decoder_tc: HTTP2DecoderHalf::new(), + decoder_ts: HTTP2DecoderHalf::new(), + } + } + + pub fn http2_encoding_fromvec(&mut self, input: &[u8], dir: Direction) { + if dir == Direction::ToClient { + self.decoder_tc.http2_encoding_fromvec(input); + } else { + self.decoder_ts.http2_encoding_fromvec(input); + } + } + + pub fn decompress<'a>( + &mut self, input: &'a [u8], output: &'a mut Vec<u8>, dir: Direction, + ) -> io::Result<&'a [u8]> { + if dir == Direction::ToClient { + return self.decoder_tc.decompress(input, output); + } else { + return self.decoder_ts.decompress(input, output); + } + } +} diff --git a/rust/src/http2/detect.rs b/rust/src/http2/detect.rs new file mode 100644 index 0000000..52b4119 --- /dev/null +++ b/rust/src/http2/detect.rs @@ -0,0 +1,1098 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::http2::{ + HTTP2Event, HTTP2Frame, HTTP2FrameTypeData, HTTP2State, HTTP2Transaction, HTTP2TransactionState, +}; +use super::parser; +use crate::core::Direction; +use crate::detect::uint::{detect_match_uint, DetectUintData}; +use std::ffi::CStr; +use std::str::FromStr; + +fn http2_tx_has_frametype( + tx: &mut HTTP2Transaction, direction: Direction, value: u8, +) -> std::os::raw::c_int { + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + if tx.frames_ts[i].header.ftype == value { + return 1; + } + } + } else { + for i in 0..tx.frames_tc.len() { + if tx.frames_tc[i].header.ftype == value { + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_has_frametype( + tx: *mut std::os::raw::c_void, direction: u8, value: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_has_frametype(tx, direction.into(), value); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_frametype( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok(x) = parser::HTTP2FrameType::from_str(s) { + return x as i32; + } + } + return -1; +} + +fn http2_tx_has_errorcode( + tx: &mut HTTP2Transaction, direction: Direction, code: u32, +) -> std::os::raw::c_int { + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + match tx.frames_ts[i].data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if goaway.errorcode == code { + return 1; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if rst.errorcode == code { + return 1; + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match tx.frames_tc[i].data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if goaway.errorcode == code { + return 1; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if rst.errorcode == code { + return 1; + } + } + _ => {} + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_has_errorcode( + tx: *mut std::os::raw::c_void, direction: u8, code: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_has_errorcode(tx, direction.into(), code); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_errorcode( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok(x) = parser::HTTP2ErrorCode::from_str(s) { + return x as i32; + } + } + return -1; +} + +fn http2_tx_get_next_priority( + tx: &mut HTTP2Transaction, direction: Direction, nb: u32, +) -> std::os::raw::c_int { + let mut pos = 0_u32; + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + match &tx.frames_ts[i].data { + HTTP2FrameTypeData::PRIORITY(prio) => { + if pos == nb { + return prio.weight as i32; + } else { + pos += 1; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(prio) = hd.priority { + if pos == nb { + return prio.weight as i32; + } else { + pos += 1; + } + } + } + _ => {} + } + } + } else { + for i in 0..tx.frames_tc.len() { + match &tx.frames_tc[i].data { + HTTP2FrameTypeData::PRIORITY(prio) => { + if pos == nb { + return prio.weight as i32; + } else { + pos += 1; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(prio) = hd.priority { + if pos == nb { + return prio.weight as i32; + } else { + pos += 1; + } + } + } + _ => {} + } + } + } + return -1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_next_priority( + tx: *mut std::os::raw::c_void, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_get_next_priority(tx, direction.into(), nb); +} + +fn http2_tx_get_next_window( + tx: &mut HTTP2Transaction, direction: Direction, nb: u32, +) -> std::os::raw::c_int { + let mut pos = 0_u32; + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + if let HTTP2FrameTypeData::WINDOWUPDATE(wu) = tx.frames_ts[i].data { + if pos == nb { + return wu.sizeinc as i32; + } else { + pos += 1; + } + } + } + } else { + for i in 0..tx.frames_tc.len() { + if let HTTP2FrameTypeData::WINDOWUPDATE(wu) = tx.frames_tc[i].data { + if pos == nb { + return wu.sizeinc as i32; + } else { + pos += 1; + } + } + } + } + return -1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_next_window( + tx: *mut std::os::raw::c_void, direction: u8, nb: u32, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_tx_get_next_window(tx, direction.into(), nb); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_settingsctx_parse( + str: *const std::os::raw::c_char, +) -> *mut std::os::raw::c_void { + let ft_name: &CStr = CStr::from_ptr(str); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = parser::http2_parse_settingsctx(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_settingsctx_free(ctx: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx as *mut parser::DetectHTTP2settingsSigCtx)); +} + +fn http2_detect_settings_match( + set: &[parser::HTTP2FrameSettings], ctx: &parser::DetectHTTP2settingsSigCtx, +) -> std::os::raw::c_int { + for e in set { + if e.id == ctx.id { + match &ctx.value { + None => { + return 1; + } + Some(x) => { + if detect_match_uint(x, e.value) { + return 1; + } + } + } + } + } + return 0; +} + +fn http2_detect_settingsctx_match( + ctx: &mut parser::DetectHTTP2settingsSigCtx, tx: &mut HTTP2Transaction, direction: Direction, +) -> std::os::raw::c_int { + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + if let HTTP2FrameTypeData::SETTINGS(set) = &tx.frames_ts[i].data { + if http2_detect_settings_match(set, ctx) != 0 { + return 1; + } + } + } + } else { + for i in 0..tx.frames_tc.len() { + if let HTTP2FrameTypeData::SETTINGS(set) = &tx.frames_tc[i].data { + if http2_detect_settings_match(set, ctx) != 0 { + return 1; + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_settingsctx_match( + ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let ctx = cast_pointer!(ctx, parser::DetectHTTP2settingsSigCtx); + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_detect_settingsctx_match(ctx, tx, direction.into()); +} + +fn http2_detect_sizeupdate_match( + blocks: &[parser::HTTP2FrameHeaderBlock], ctx: &DetectUintData<u64>, +) -> std::os::raw::c_int { + for block in blocks.iter() { + if block.error == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + && detect_match_uint(ctx, block.sizeupdate) + { + return 1; + } + } + return 0; +} + +fn http2_header_blocks(frame: &HTTP2Frame) -> Option<&[parser::HTTP2FrameHeaderBlock]> { + match &frame.data { + HTTP2FrameTypeData::HEADERS(hd) => { + return Some(&hd.blocks); + } + HTTP2FrameTypeData::CONTINUATION(hd) => { + return Some(&hd.blocks); + } + HTTP2FrameTypeData::PUSHPROMISE(hd) => { + return Some(&hd.blocks); + } + _ => {} + } + return None; +} + +fn http2_detect_sizeupdatectx_match( + ctx: &mut DetectUintData<u64>, tx: &mut HTTP2Transaction, direction: Direction, +) -> std::os::raw::c_int { + if direction == Direction::ToServer { + for i in 0..tx.frames_ts.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) { + if http2_detect_sizeupdate_match(blocks, ctx) != 0 { + return 1; + } + } + } + } else { + for i in 0..tx.frames_tc.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) { + if http2_detect_sizeupdate_match(blocks, ctx) != 0 { + return 1; + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_detect_sizeupdatectx_match( + ctx: *const std::os::raw::c_void, tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let ctx = cast_pointer!(ctx, DetectUintData<u64>); + let tx = cast_pointer!(tx, HTTP2Transaction); + return http2_detect_sizeupdatectx_match(ctx, tx, direction.into()); +} + +//TODOask better syntax between rs_http2_tx_get_header_name in argument +// and rs_http2_detect_sizeupdatectx_match explicitly casting +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header_name( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut pos = 0_u32; + match direction.into() { + Direction::ToServer => { + for i in 0..tx.frames_ts.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) { + if nb < pos + blocks.len() as u32 { + let value = &blocks[(nb - pos) as usize].name; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos += blocks.len() as u32; + } + } + } + } + Direction::ToClient => { + for i in 0..tx.frames_tc.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) { + if nb < pos + blocks.len() as u32 { + let value = &blocks[(nb - pos) as usize].name; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos += blocks.len() as u32; + } + } + } + } + } + return 0; +} + +fn http2_frames_get_header_firstvalue<'a>( + tx: &'a mut HTTP2Transaction, direction: Direction, name: &str, +) -> Result<&'a [u8], ()> { + let frames = if direction == Direction::ToServer { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + if block.name == name.as_bytes() { + return Ok(&block.value); + } + } + } + } + return Err(()); +} + +// same as http2_frames_get_header_value but returns a new Vec +// instead of using the transaction to store the result slice +pub fn http2_frames_get_header_value_vec( + tx: &HTTP2Transaction, direction: Direction, name: &str, +) -> Result<Vec<u8>, ()> { + let mut found = 0; + let mut vec = Vec::new(); + let frames = if direction == Direction::ToServer { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + if block.name == name.as_bytes() { + if found == 0 { + vec.extend_from_slice(&block.value); + found = 1; + } else if found == 1 { + vec.extend_from_slice(&[b',', b' ']); + vec.extend_from_slice(&block.value); + found = 2; + } else { + vec.extend_from_slice(&[b',', b' ']); + vec.extend_from_slice(&block.value); + } + } + } + } + } + if found == 0 { + return Err(()); + } else { + return Ok(vec); + } +} + +fn http2_frames_get_header_value<'a>( + tx: &'a mut HTTP2Transaction, direction: Direction, name: &str, +) -> Result<&'a [u8], ()> { + let mut found = 0; + let mut vec = Vec::new(); + let mut single: Result<&[u8], ()> = Err(()); + let frames = if direction == Direction::ToServer { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + if block.name == name.as_bytes() { + if found == 0 { + single = Ok(&block.value); + found = 1; + } else if found == 1 { + if let Ok(s) = single { + vec.extend_from_slice(s); + } + vec.extend_from_slice(&[b',', b' ']); + vec.extend_from_slice(&block.value); + found = 2; + } else { + vec.extend_from_slice(&[b',', b' ']); + vec.extend_from_slice(&block.value); + } + } + } + } + } + if found == 0 { + return Err(()); + } else if found == 1 { + return single; + } else { + tx.escaped.push(vec); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + return Ok(value); + } +} + +fn http2_tx_get_req_line(tx: &mut HTTP2Transaction) { + if !tx.req_line.is_empty() { + return; + } + let empty = Vec::new(); + let mut req_line = Vec::new(); + let method = + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":method") { + value + } else { + &empty + }; + req_line.extend(method); + req_line.push(b' '); + + let uri = + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":path") { + value + } else { + &empty + }; + req_line.extend(uri); + req_line.extend(b" HTTP/2\r\n"); + tx.req_line.extend(req_line) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_request_line( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + http2_tx_get_req_line(tx); + *buffer = tx.req_line.as_ptr(); //unsafe + *buffer_len = tx.req_line.len() as u32; + return 1; +} + +fn http2_tx_get_resp_line(tx: &mut HTTP2Transaction) { + if !tx.resp_line.is_empty() { + return; + } + let empty = Vec::new(); + let mut resp_line : Vec<u8> = Vec::new(); + + let status = + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToClient, ":status") { + value + } else { + &empty + }; + resp_line.extend(b"HTTP/2 "); + resp_line.extend(status); + resp_line.extend(b"\r\n"); + tx.resp_line.extend(resp_line) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_response_line( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + http2_tx_get_resp_line(tx); + *buffer = tx.resp_line.as_ptr(); //unsafe + *buffer_len = tx.resp_line.len() as u32; + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_uri( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":path") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_method( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToServer, ":method") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_host( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, ":authority") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +fn http2_lower(value: &[u8]) -> Option<Vec<u8>> { + for i in 0..value.len() { + if value[i].is_ascii_uppercase() { + // we got at least one upper character, need to transform + let mut vec: Vec<u8> = Vec::with_capacity(value.len()); + vec.extend_from_slice(value); + for e in &mut vec { + e.make_ascii_lowercase(); + } + return Some(vec); + } + } + return None; +} + +// returns a tuple with the value and its size +fn http2_normalize_host(value: &[u8]) -> &[u8] { + match value.iter().position(|&x| x == b'@') { + Some(i) => { + let value = &value[i+1..]; + match value.iter().position(|&x| x == b':') { + Some(i) => { + return &value[..i]; + } + None => { + return value; + } + } + } + None => { + match value.iter().position(|&x| x == b':') { + Some(i) => { + return &value[..i]; + } + None => { + return value; + } + } + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_host_norm( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, ":authority") { + let r = http2_normalize_host(value); + // r is a tuple with the value and its size + // this is useful when we only take a substring (before the port) + match http2_lower(r) { + Some(normval) => { + // In case we needed some normalization, + // the transaction needs to take ownership of this normalized host + tx.escaped.push(normval); + let idx = tx.escaped.len() - 1; + let resvalue = &tx.escaped[idx]; + *buffer = resvalue.as_ptr(); //unsafe + *buffer_len = resvalue.len() as u32; + return 1; + } + None => { + *buffer = r.as_ptr(); //unsafe + *buffer_len = r.len() as u32; + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_useragent( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, "user-agent") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_status( + tx: &mut HTTP2Transaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Ok(value) = http2_frames_get_header_firstvalue(tx, Direction::ToClient, ":status") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_cookie( + tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if direction == Direction::ToServer.into() { + if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToServer, "cookie") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + } else if let Ok(value) = http2_frames_get_header_value(tx, Direction::ToClient, "set-cookie") { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header_value( + tx: &mut HTTP2Transaction, direction: u8, strname: *const std::os::raw::c_char, + buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let hname: &CStr = CStr::from_ptr(strname); //unsafe + if let Ok(s) = hname.to_str() { + if let Ok(value) = http2_frames_get_header_value(tx, direction.into(), &s.to_lowercase()) { + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + } + return 0; +} + +fn http2_escape_header(blocks: &[parser::HTTP2FrameHeaderBlock], i: u32) -> Vec<u8> { + //minimum size + 2 for escapes + let normalsize = blocks[i as usize].value.len() + 2 + blocks[i as usize].name.len(); + let mut vec = Vec::with_capacity(normalsize); + vec.extend_from_slice(&blocks[i as usize].name); + vec.extend_from_slice(&[b':', b' ']); + vec.extend_from_slice(&blocks[i as usize].value); + return vec; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header_names( + tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut vec = vec![b'\r', b'\n']; + let frames = if direction & Direction::ToServer as u8 != 0 { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + // we do not escape linefeeds in headers names + vec.extend_from_slice(&block.name); + vec.extend_from_slice(&[b'\r', b'\n']); + } + } + } + if vec.len() > 2 { + vec.extend_from_slice(&[b'\r', b'\n']); + tx.escaped.push(vec); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +fn http2_header_iscookie(direction: Direction, hname: &[u8]) -> bool { + if let Ok(s) = std::str::from_utf8(hname) { + if direction == Direction::ToServer { + if s.to_lowercase() == "cookie" { + return true; + } + } else if s.to_lowercase() == "set-cookie" { + return true; + } + } + return false; +} + +fn http2_header_trimspaces(value: &[u8]) -> &[u8] { + let mut start = 0; + let mut end = value.len(); + while start < value.len() { + if value[start] == b' ' || value[start] == b'\t' { + start += 1; + } else { + break; + } + } + while end > start { + if value[end - 1] == b' ' || value[end - 1] == b'\t' { + end -= 1; + } else { + break; + } + } + return &value[start..end]; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_headers( + tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut vec = Vec::new(); + let frames = if direction & Direction::ToServer as u8 != 0 { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + if !http2_header_iscookie(direction.into(), &block.name) { + // we do not escape linefeeds nor : in headers names + vec.extend_from_slice(&block.name); + vec.extend_from_slice(&[b':', b' ']); + vec.extend_from_slice(http2_header_trimspaces(&block.value)); + vec.extend_from_slice(&[b'\r', b'\n']); + } + } + } + } + if !vec.is_empty() { + tx.escaped.push(vec); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_headers_raw( + tx: &mut HTTP2Transaction, direction: u8, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut vec = Vec::new(); + let frames = if direction & Direction::ToServer as u8 != 0 { + &tx.frames_ts + } else { + &tx.frames_tc + }; + for frame in frames { + if let Some(blocks) = http2_header_blocks(frame) { + for block in blocks.iter() { + // we do not escape linefeeds nor : in headers names + vec.extend_from_slice(&block.name); + vec.extend_from_slice(&[b':', b' ']); + vec.extend_from_slice(&block.value); + vec.extend_from_slice(&[b'\r', b'\n']); + } + } + } + if !vec.is_empty() { + tx.escaped.push(vec); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_header( + tx: &mut HTTP2Transaction, direction: u8, nb: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + let mut pos = 0_u32; + match direction.into() { + Direction::ToServer => { + for i in 0..tx.frames_ts.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_ts[i]) { + if nb < pos + blocks.len() as u32 { + let ehdr = http2_escape_header(blocks, nb - pos); + tx.escaped.push(ehdr); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos += blocks.len() as u32; + } + } + } + } + Direction::ToClient => { + for i in 0..tx.frames_tc.len() { + if let Some(blocks) = http2_header_blocks(&tx.frames_tc[i]) { + if nb < pos + blocks.len() as u32 { + let ehdr = http2_escape_header(blocks, nb - pos); + tx.escaped.push(ehdr); + let idx = tx.escaped.len() - 1; + let value = &tx.escaped[idx]; + *buffer = value.as_ptr(); //unsafe + *buffer_len = value.len() as u32; + return 1; + } else { + pos += blocks.len() as u32; + } + } + } + } + } + return 0; +} + +fn http2_tx_set_header(state: &mut HTTP2State, name: &[u8], input: &[u8]) { + let head = parser::HTTP2FrameHeader { + length: 0, + ftype: parser::HTTP2FrameType::Headers as u8, + flags: 0, + reserved: 0, + stream_id: 1, + }; + let mut blocks = Vec::new(); + let b = parser::HTTP2FrameHeaderBlock { + name: name.to_vec(), + value: input.to_vec(), + error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + blocks.push(b); + let hs = parser::HTTP2FrameHeaders { + padlength: None, + priority: None, + blocks, + }; + let txdata = HTTP2FrameTypeData::HEADERS(hs); + let tx = state.find_or_create_tx(&head, &txdata, Direction::ToServer).unwrap(); + tx.frames_ts.push(HTTP2Frame { + header: head, + data: txdata, + }); + //we do not expect more data from client + tx.state = HTTP2TransactionState::HTTP2StateHalfClosedClient; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_set_method( + state: &mut HTTP2State, buffer: *const u8, buffer_len: u32, +) { + let slice = build_slice!(buffer, buffer_len as usize); + http2_tx_set_header(state, ":method".as_bytes(), slice) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_set_uri( + state: &mut HTTP2State, buffer: *const u8, buffer_len: u32, +) { + let slice = build_slice!(buffer, buffer_len as usize); + http2_tx_set_header(state, ":path".as_bytes(), slice) +} + +fn http2_tx_set_settings(state: &mut HTTP2State, input: &[u8]) { + match base64::decode(input) { + Ok(dec) => { + if dec.len() % 6 != 0 { + state.set_event(HTTP2Event::InvalidHTTP1Settings); + } + + let head = parser::HTTP2FrameHeader { + length: dec.len() as u32, + ftype: parser::HTTP2FrameType::Settings as u8, + flags: 0, + reserved: 0, + stream_id: 0, + }; + + match parser::http2_parse_frame_settings(&dec) { + Ok((_, set)) => { + let txdata = HTTP2FrameTypeData::SETTINGS(set); + let tx = state.find_or_create_tx(&head, &txdata, Direction::ToServer).unwrap(); + tx.frames_ts.push(HTTP2Frame { + header: head, + data: txdata, + }); + } + Err(_) => { + state.set_event(HTTP2Event::InvalidHTTP1Settings); + } + } + } + Err(_) => { + state.set_event(HTTP2Event::InvalidHTTP1Settings); + } + } +} + +fn http2_caseinsensitive_cmp(s1: &[u8], s2: &str) -> bool { + if let Ok(s) = std::str::from_utf8(s1) { + return s.to_lowercase() == s2; + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_add_header( + state: &mut HTTP2State, name: *const u8, name_len: u32, value: *const u8, value_len: u32, +) { + let slice_name = build_slice!(name, name_len as usize); + let slice_value = build_slice!(value, value_len as usize); + if slice_name == "HTTP2-Settings".as_bytes() { + http2_tx_set_settings(state, slice_value) + } else if http2_caseinsensitive_cmp(slice_name, "host") { + http2_tx_set_header(state, ":authority".as_bytes(), slice_value) + } else { + http2_tx_set_header(state, slice_name, slice_value) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_http2_normalize_host() { + let buf0 = "aBC.com:1234".as_bytes(); + let r0 = http2_normalize_host(buf0); + assert_eq!(r0, "aBC.com".as_bytes().to_vec()); + let buf1 = "oisf.net".as_bytes(); + let r1 = http2_normalize_host(buf1); + assert_eq!(r1, "oisf.net".as_bytes().to_vec()); + let buf2 = "localhost:3000".as_bytes(); + let r2 = http2_normalize_host(buf2); + assert_eq!(r2, "localhost".as_bytes().to_vec()); + let buf3 = "user:pass@localhost".as_bytes(); + let r3 = http2_normalize_host(buf3); + assert_eq!(r3, "localhost".as_bytes().to_vec()); + let buf4 = "user:pass@localhost:123".as_bytes(); + let r4 = http2_normalize_host(buf4); + assert_eq!(r4, "localhost".as_bytes().to_vec()); + } + + #[test] + fn test_http2_header_trimspaces() { + let buf0 = "nospaces".as_bytes(); + let r0 = http2_header_trimspaces(buf0); + assert_eq!(r0, "nospaces".as_bytes()); + let buf1 = " spaces\t".as_bytes(); + let r1 = http2_header_trimspaces(buf1); + assert_eq!(r1, "spaces".as_bytes()); + let buf2 = " \t".as_bytes(); + let r2 = http2_header_trimspaces(buf2); + assert_eq!(r2, "".as_bytes()); + } + + #[test] + fn test_http2_frames_get_header_value() { + let mut tx = HTTP2Transaction::new(); + let head = parser::HTTP2FrameHeader { + length: 0, + ftype: parser::HTTP2FrameType::Headers as u8, + flags: 0, + reserved: 0, + stream_id: 1, + }; + let mut blocks = Vec::new(); + let b = parser::HTTP2FrameHeaderBlock { + name: "Host".as_bytes().to_vec(), + value: "abc.com".as_bytes().to_vec(), + error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + blocks.push(b); + let b2 = parser::HTTP2FrameHeaderBlock { + name: "Host".as_bytes().to_vec(), + value: "efg.net".as_bytes().to_vec(), + error: parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + blocks.push(b2); + let hs = parser::HTTP2FrameHeaders { + padlength: None, + priority: None, + blocks, + }; + let txdata = HTTP2FrameTypeData::HEADERS(hs); + tx.frames_ts.push(HTTP2Frame { + header: head, + data: txdata, + }); + match http2_frames_get_header_value(&mut tx, Direction::ToServer, "Host") { + Ok(x) => { + assert_eq!(x, "abc.com, efg.net".as_bytes()); + } + Err(e) => { + panic!("Result should not have been an error: {:?}", e); + } + } + } +} diff --git a/rust/src/http2/http2.rs b/rust/src/http2/http2.rs new file mode 100644 index 0000000..b62ccb9 --- /dev/null +++ b/rust/src/http2/http2.rs @@ -0,0 +1,1408 @@ +/* Copyright (C) 2020-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::decompression; +use super::detect; +use super::parser; +use super::range; + +use crate::applayer::{self, *}; +use crate::conf::conf_get; +use crate::core::*; +use crate::filecontainer::*; +use crate::filetracker::*; +use nom7::Err; +use std; +use std::collections::VecDeque; +use std::ffi::CString; +use std::fmt; +use std::io; + +static mut ALPROTO_HTTP2: AppProto = ALPROTO_UNKNOWN; + +const HTTP2_DEFAULT_MAX_FRAME_SIZE: u32 = 16384; +const HTTP2_MAX_HANDLED_FRAME_SIZE: usize = 65536; +const HTTP2_MIN_HANDLED_FRAME_SIZE: usize = 256; + +pub static mut SURICATA_HTTP2_FILE_CONFIG: Option<&'static SuricataFileContext> = None; + +#[no_mangle] +pub extern "C" fn rs_http2_init(context: &'static mut SuricataFileContext) { + unsafe { + SURICATA_HTTP2_FILE_CONFIG = Some(context); + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq)] +pub enum HTTP2ConnectionState { + Http2StateInit = 0, + Http2StateMagicDone = 1, +} + +const HTTP2_FRAME_HEADER_LEN: usize = 9; +const HTTP2_MAGIC_LEN: usize = 24; +const HTTP2_FRAME_GOAWAY_LEN: usize = 4; +const HTTP2_FRAME_RSTSTREAM_LEN: usize = 4; +const HTTP2_FRAME_PRIORITY_LEN: usize = 5; +const HTTP2_FRAME_WINDOWUPDATE_LEN: usize = 4; +pub static mut HTTP2_MAX_TABLESIZE: u32 = 65536; // 0x10000 +// maximum size of reassembly for header + continuation +static mut HTTP2_MAX_REASS: usize = 102400; +static mut HTTP2_MAX_STREAMS: usize = 4096; // 0x1000 + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2FrameUnhandledReason { + UnknownType = 0, + TooLong = 1, + ParsingError = 2, + Incomplete = 3, +} + +impl fmt::Display for HTTP2FrameUnhandledReason { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Debug)] +pub struct HTTP2FrameUnhandled { + pub reason: HTTP2FrameUnhandledReason, +} + +#[derive(Debug)] +pub enum HTTP2FrameTypeData { + PRIORITY(parser::HTTP2FramePriority), + GOAWAY(parser::HTTP2FrameGoAway), + RSTSTREAM(parser::HTTP2FrameRstStream), + SETTINGS(Vec<parser::HTTP2FrameSettings>), + WINDOWUPDATE(parser::HTTP2FrameWindowUpdate), + HEADERS(parser::HTTP2FrameHeaders), + PUSHPROMISE(parser::HTTP2FramePushPromise), + CONTINUATION(parser::HTTP2FrameContinuation), + PING, + DATA, + //not a defined frame + UNHANDLED(HTTP2FrameUnhandled), +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2TransactionState { + HTTP2StateIdle = 0, + HTTP2StateOpen = 1, + HTTP2StateReserved = 2, + HTTP2StateDataClient = 3, + HTTP2StateHalfClosedClient = 4, + HTTP2StateDataServer = 5, + HTTP2StateHalfClosedServer = 6, + HTTP2StateClosed = 7, + //not a RFC-defined state, used for stream 0 frames applying to the global connection + HTTP2StateGlobal = 8, + //not a RFC-defined state, dropping this old tx because we have too many + HTTP2StateTodrop = 9, +} + +#[derive(Debug)] +pub struct HTTP2Frame { + pub header: parser::HTTP2FrameHeader, + pub data: HTTP2FrameTypeData, +} + +#[derive(Debug)] +pub struct HTTP2Transaction { + tx_id: u64, + pub stream_id: u32, + pub state: HTTP2TransactionState, + child_stream_id: u32, + + pub frames_tc: Vec<HTTP2Frame>, + pub frames_ts: Vec<HTTP2Frame>, + + decoder: decompression::HTTP2Decoder, + pub file_range: *mut HttpRangeContainerBlock, + + pub tx_data: AppLayerTxData, + pub ft_tc: FileTransferTracker, + pub ft_ts: FileTransferTracker, + + //temporary escaped header for detection + //must be attached to transaction for memory management (be freed at the right time) + pub escaped: Vec<Vec<u8>>, + pub req_line: Vec<u8>, + pub resp_line: Vec<u8>, +} + +impl Transaction for HTTP2Transaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +impl Default for HTTP2Transaction { + fn default() -> Self { + Self::new() + } +} + +impl HTTP2Transaction { + pub fn new() -> Self { + Self { + tx_id: 0, + stream_id: 0, + child_stream_id: 0, + state: HTTP2TransactionState::HTTP2StateIdle, + frames_tc: Vec::new(), + frames_ts: Vec::new(), + decoder: decompression::HTTP2Decoder::new(), + file_range: std::ptr::null_mut(), + tx_data: AppLayerTxData::new(), + ft_tc: FileTransferTracker::new(), + ft_ts: FileTransferTracker::new(), + escaped: Vec::with_capacity(16), + req_line: Vec::new(), + resp_line: Vec::new(), + } + } + + pub fn free(&mut self) { + if !self.file_range.is_null() { + if let Some(c) = unsafe { SC } { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + //TODO get a file container instead of NULL + (c.HTPFileCloseHandleRange)( + sfcm.files_sbcfg, + std::ptr::null_mut(), + 0, + self.file_range, + std::ptr::null_mut(), + 0, + ); + (c.HttpRangeFreeBlock)(self.file_range); + self.file_range = std::ptr::null_mut(); + } + } + } + } + + pub fn set_event(&mut self, event: HTTP2Event) { + self.tx_data.set_event(event as u8); + } + + fn handle_headers(&mut self, blocks: &[parser::HTTP2FrameHeaderBlock], dir: Direction) { + let mut authority = None; + let mut host = None; + for block in blocks { + if block.name == b"content-encoding" { + self.decoder.http2_encoding_fromvec(&block.value, dir); + } else if block.name.eq_ignore_ascii_case(b":authority") { + authority = Some(&block.value); + if block.value.iter().any(|&x| x == b'@') { + // it is forbidden by RFC 9113 to have userinfo in this field + // when in HTTP1 we can have user:password@domain.com + self.set_event(HTTP2Event::UserinfoInUri); + } + } else if block.name.eq_ignore_ascii_case(b"host") { + host = Some(&block.value); + } + } + if let Some(a) = authority { + if let Some(h) = host { + if !a.eq_ignore_ascii_case(h) { + // The event is triggered only if both headers + // are in the same frame to avoid excessive + // complexity at runtime. + self.set_event(HTTP2Event::AuthorityHostMismatch); + } + } + } + } + + pub fn update_file_flags(&mut self, flow_file_flags: u16) { + self.ft_ts.file_flags = unsafe { FileFlowFlagsToFlags(flow_file_flags, STREAM_TOSERVER) }; + self.ft_tc.file_flags = unsafe { FileFlowFlagsToFlags(flow_file_flags, STREAM_TOCLIENT) }; + } + + fn decompress<'a>( + &'a mut self, input: &'a [u8], dir: Direction, sfcm: &'static SuricataFileContext, over: bool, flow: *const Flow, + ) -> io::Result<()> { + let mut output = Vec::with_capacity(decompression::HTTP2_DECOMPRESSION_CHUNK_SIZE); + let decompressed = self.decoder.decompress(input, &mut output, dir)?; + let xid: u32 = self.tx_id as u32; + if dir == Direction::ToClient { + self.ft_tc.tx_id = self.tx_id - 1; + // Check that we are at the beginning of the file + if !self.ft_tc.is_initialized() { + // we are now sure that new_chunk will open a file + // even if it may close it right afterwards + self.tx_data.incr_files_opened(); + if let Ok(value) = detect::http2_frames_get_header_value_vec( + self, + Direction::ToClient, + "content-range", + ) { + match range::http2_parse_check_content_range(&value) { + Ok((_, v)) => { + range::http2_range_open(self, &v, flow, sfcm, Direction::ToClient, decompressed); + if over && !self.file_range.is_null() { + range::http2_range_close(self, Direction::ToClient, &[]) + } + } + _ => { + self.set_event(HTTP2Event::InvalidRange); + } + } + } + } else if !self.file_range.is_null() { + if over { + range::http2_range_close(self, Direction::ToClient, decompressed) + } else { + range::http2_range_append(sfcm, self.file_range, decompressed) + } + } + self.ft_tc.new_chunk( + sfcm, + b"", + decompressed, + self.ft_tc.tracked, //offset = append + decompressed.len() as u32, + 0, + over, + &xid, + ); + } else { + self.ft_ts.tx_id = self.tx_id - 1; + if !self.ft_ts.file_open { + self.tx_data.incr_files_opened(); + } + self.ft_ts.new_chunk( + sfcm, + b"", + decompressed, + self.ft_ts.tracked, //offset = append + decompressed.len() as u32, + 0, + over, + &xid, + ); + }; + return Ok(()); + } + + fn handle_frame( + &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction, + ) { + //handle child_stream_id changes + match data { + HTTP2FrameTypeData::PUSHPROMISE(hs) => { + if dir == Direction::ToClient { + //we could set an event if self.child_stream_id != 0 + if header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { + self.child_stream_id = hs.stream_id; + } + self.state = HTTP2TransactionState::HTTP2StateReserved; + } + self.handle_headers(&hs.blocks, dir); + } + HTTP2FrameTypeData::CONTINUATION(hs) => { + if dir == Direction::ToClient + && header.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS != 0 + { + self.child_stream_id = 0; + } + self.handle_headers(&hs.blocks, dir); + } + HTTP2FrameTypeData::HEADERS(hs) => { + if dir == Direction::ToClient { + self.child_stream_id = 0; + } + self.handle_headers(&hs.blocks, dir); + } + HTTP2FrameTypeData::RSTSTREAM(_) => { + self.child_stream_id = 0; + } + _ => {} + } + //handle closing state changes + match data { + HTTP2FrameTypeData::HEADERS(_) | HTTP2FrameTypeData::DATA => { + if header.flags & parser::HTTP2_FLAG_HEADER_EOS != 0 { + match self.state { + HTTP2TransactionState::HTTP2StateHalfClosedClient + | HTTP2TransactionState::HTTP2StateDataServer => { + if dir == Direction::ToClient { + self.state = HTTP2TransactionState::HTTP2StateClosed; + } + } + HTTP2TransactionState::HTTP2StateHalfClosedServer => { + if dir == Direction::ToServer { + self.state = HTTP2TransactionState::HTTP2StateClosed; + } + } + // do not revert back to a half closed state + HTTP2TransactionState::HTTP2StateClosed => {} + HTTP2TransactionState::HTTP2StateGlobal => {} + _ => { + if dir == Direction::ToClient { + self.state = HTTP2TransactionState::HTTP2StateHalfClosedServer; + } else { + self.state = HTTP2TransactionState::HTTP2StateHalfClosedClient; + } + } + } + } else if header.ftype == parser::HTTP2FrameType::Data as u8 { + //not end of stream + if dir == Direction::ToServer { + if self.state < HTTP2TransactionState::HTTP2StateDataClient { + self.state = HTTP2TransactionState::HTTP2StateDataClient; + } + } else if self.state < HTTP2TransactionState::HTTP2StateDataServer { + self.state = HTTP2TransactionState::HTTP2StateDataServer; + } + } + } + _ => {} + } + } +} + +impl Drop for HTTP2Transaction { + fn drop(&mut self) { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + self.ft_ts.file.free(sfcm); + self.ft_tc.file.free(sfcm); + } + self.free(); + } +} + +#[derive(AppLayerEvent)] +pub enum HTTP2Event { + InvalidFrameHeader, + InvalidClientMagic, + InvalidFrameData, + InvalidHeader, + InvalidFrameLength, + ExtraHeaderData, + LongFrameData, + StreamIdReuse, + InvalidHTTP1Settings, + FailedDecompression, + InvalidRange, + HeaderIntegerOverflow, + TooManyStreams, + AuthorityHostMismatch, + UserinfoInUri, + ReassemblyLimitReached, +} + +pub struct HTTP2DynTable { + pub table: Vec<parser::HTTP2FrameHeaderBlock>, + pub current_size: usize, + pub max_size: usize, + pub overflow: u8, +} + +impl Default for HTTP2DynTable { + fn default() -> Self { + Self::new() + } +} + +impl HTTP2DynTable { + pub fn new() -> Self { + Self { + table: Vec::with_capacity(64), + current_size: 0, + max_size: 4096, //default value + overflow: 0, + } + } +} + +#[derive(Default)] +struct HTTP2HeaderReassemblyBuffer { + data: Vec<u8>, + stream_id: u32, +} + +pub struct HTTP2State { + state_data: AppLayerStateData, + tx_id: u64, + request_frame_size: u32, + response_frame_size: u32, + dynamic_headers_ts: HTTP2DynTable, + dynamic_headers_tc: HTTP2DynTable, + transactions: VecDeque<HTTP2Transaction>, + progress: HTTP2ConnectionState, + + c2s_buf: HTTP2HeaderReassemblyBuffer, + s2c_buf: HTTP2HeaderReassemblyBuffer, +} + +impl State<HTTP2Transaction> for HTTP2State { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&HTTP2Transaction> { + self.transactions.get(index) + } +} + +impl Default for HTTP2State { + fn default() -> Self { + Self::new() + } +} + +impl HTTP2State { + pub fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + tx_id: 0, + request_frame_size: 0, + response_frame_size: 0, + // the headers are encoded on one byte + // with a fixed number of static headers, and + // a variable number of dynamic headers + dynamic_headers_ts: HTTP2DynTable::new(), + dynamic_headers_tc: HTTP2DynTable::new(), + transactions: VecDeque::new(), + progress: HTTP2ConnectionState::Http2StateInit, + c2s_buf: HTTP2HeaderReassemblyBuffer::default(), + s2c_buf: HTTP2HeaderReassemblyBuffer::default(), + } + } + + pub fn free(&mut self) { + // this should be in HTTP2Transaction::free + // but we need state's file container cf https://redmine.openinfosecfoundation.org/issues/4444 + for tx in &mut self.transactions { + if !tx.file_range.is_null() { + if let Some(c) = unsafe { SC } { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + (c.HTPFileCloseHandleRange)( + sfcm.files_sbcfg, + &mut tx.ft_tc.file, + 0, + tx.file_range, + std::ptr::null_mut(), + 0, + ); + (c.HttpRangeFreeBlock)(tx.file_range); + tx.file_range = std::ptr::null_mut(); + } + } + } + } + self.transactions.clear(); + } + + pub fn set_event(&mut self, event: HTTP2Event) { + let len = self.transactions.len(); + if len == 0 { + return; + } + let tx = &mut self.transactions[len - 1]; + tx.tx_data.set_event(event as u8); + } + + // Free a transaction by ID. + 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 = &mut self.transactions[i]; + if tx.tx_id == tx_id + 1 { + found = true; + index = i; + // this should be in HTTP2Transaction::free + // but we need state's file container cf https://redmine.openinfosecfoundation.org/issues/4444 + if !tx.file_range.is_null() { + if let Some(c) = unsafe { SC } { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + (c.HTPFileCloseHandleRange)( + sfcm.files_sbcfg, + &mut tx.ft_tc.file, + 0, + tx.file_range, + std::ptr::null_mut(), + 0, + ); + (c.HttpRangeFreeBlock)(tx.file_range); + tx.file_range = std::ptr::null_mut(); + } + } + } + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&HTTP2Transaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + tx.tx_data.update_file_flags(self.state_data.file_flags); + tx.update_file_flags(tx.tx_data.file_flags); + return Some(tx); + } + } + return None; + } + + fn find_tx_index(&mut self, sid: u32) -> usize { + for i in 0..self.transactions.len() { + //reverse order should be faster + let idx = self.transactions.len() - 1 - i; + if sid == self.transactions[idx].stream_id { + return idx + 1; + } + } + return 0; + } + + fn find_child_stream_id(&mut self, sid: u32) -> u32 { + for i in 0..self.transactions.len() { + //reverse order should be faster + if sid == self.transactions[self.transactions.len() - 1 - i].stream_id { + if self.transactions[self.transactions.len() - 1 - i].child_stream_id > 0 { + return self.transactions[self.transactions.len() - 1 - i].child_stream_id; + } + return sid; + } + } + return sid; + } + + fn create_global_tx(&mut self) -> &mut HTTP2Transaction { + //special transaction with only one frame + //as it affects the global connection, there is no end to it + let mut tx = HTTP2Transaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + tx.state = HTTP2TransactionState::HTTP2StateGlobal; + tx.tx_data.update_file_flags(self.state_data.file_flags); + // TODO can this tx hold files? + tx.tx_data.file_tx = STREAM_TOSERVER|STREAM_TOCLIENT; // might hold files in both directions + tx.update_file_flags(tx.tx_data.file_flags); + self.transactions.push_back(tx); + return self.transactions.back_mut().unwrap(); + } + + pub fn find_or_create_tx( + &mut self, header: &parser::HTTP2FrameHeader, data: &HTTP2FrameTypeData, dir: Direction, + ) -> Option<&mut HTTP2Transaction> { + if header.stream_id == 0 { + if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { + for tx_old in &mut self.transactions { + if tx_old.state == HTTP2TransactionState::HTTP2StateTodrop { + // loop was already run + break; + } + tx_old.set_event(HTTP2Event::TooManyStreams); + // use a distinct state, even if we do not log it + tx_old.state = HTTP2TransactionState::HTTP2StateTodrop; + } + return None; + } + return Some(self.create_global_tx()); + } + let sid = match data { + //yes, the right stream_id for Suricata is not the header one + HTTP2FrameTypeData::PUSHPROMISE(hs) => hs.stream_id, + HTTP2FrameTypeData::CONTINUATION(_) => { + if dir == Direction::ToClient { + //continuation of a push promise + self.find_child_stream_id(header.stream_id) + } else { + header.stream_id + } + } + _ => header.stream_id, + }; + let index = self.find_tx_index(sid); + if index > 0 { + if self.transactions[index - 1].state == HTTP2TransactionState::HTTP2StateClosed { + //these frames can be received in this state for a short period + if header.ftype != parser::HTTP2FrameType::RstStream as u8 + && header.ftype != parser::HTTP2FrameType::WindowUpdate as u8 + && header.ftype != parser::HTTP2FrameType::Priority as u8 + { + self.set_event(HTTP2Event::StreamIdReuse); + } + } + + let tx = &mut self.transactions[index - 1]; + tx.tx_data.update_file_flags(self.state_data.file_flags); + tx.update_file_flags(tx.tx_data.file_flags); + return Some(tx); + } else { + // do not use SETTINGS_MAX_CONCURRENT_STREAMS as it can grow too much + if self.transactions.len() >= unsafe { HTTP2_MAX_STREAMS } { + for tx_old in &mut self.transactions { + if tx_old.state == HTTP2TransactionState::HTTP2StateTodrop { + // loop was already run + break; + } + tx_old.set_event(HTTP2Event::TooManyStreams); + // use a distinct state, even if we do not log it + tx_old.state = HTTP2TransactionState::HTTP2StateTodrop; + } + return None; + } + let mut tx = HTTP2Transaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + tx.stream_id = sid; + tx.state = HTTP2TransactionState::HTTP2StateOpen; + tx.tx_data.update_file_flags(self.state_data.file_flags); + tx.update_file_flags(tx.tx_data.file_flags); + tx.tx_data.file_tx = STREAM_TOSERVER|STREAM_TOCLIENT; // might hold files in both directions + self.transactions.push_back(tx); + return Some(self.transactions.back_mut().unwrap()); + } + } + + fn process_headers(&mut self, blocks: &Vec<parser::HTTP2FrameHeaderBlock>, dir: Direction) { + let (mut update, mut sizeup) = (false, 0); + for block in blocks { + if block.error >= parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeError { + self.set_event(HTTP2Event::InvalidHeader); + } else if block.error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate + { + update = true; + if block.sizeupdate > sizeup { + sizeup = block.sizeupdate; + } + } else if block.error + == parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow + { + self.set_event(HTTP2Event::HeaderIntegerOverflow); + } + } + if update { + //borrow checker forbids to pass directly dyn_headers + let dyn_headers = if dir == Direction::ToClient { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + dyn_headers.max_size = sizeup as usize; + } + } + + fn parse_frame_data( + &mut self, head: &parser::HTTP2FrameHeader, input: &[u8], complete: bool, dir: Direction, + reass_limit_reached: &mut bool, + ) -> HTTP2FrameTypeData { + let ftype = head.ftype; + let hflags = head.flags; + match num::FromPrimitive::from_u8(ftype) { + Some(parser::HTTP2FrameType::GoAway) => { + if input.len() < HTTP2_FRAME_GOAWAY_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } + match parser::http2_parse_frame_goaway(input) { + Ok((_, goaway)) => { + return HTTP2FrameTypeData::GOAWAY(goaway); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::Settings) => { + match parser::http2_parse_frame_settings(input) { + Ok((_, set)) => { + for e in &set { + if e.id == parser::HTTP2SettingsId::HeaderTableSize { + //reverse order as this is what we accept from the other endpoint + let dyn_headers = if dir == Direction::ToClient { + &mut self.dynamic_headers_ts + } else { + &mut self.dynamic_headers_tc + }; + dyn_headers.max_size = e.value as usize; + if e.value > unsafe { HTTP2_MAX_TABLESIZE } { + //mark potential overflow + dyn_headers.overflow = 1; + } else { + //reset in case peer set a lower value, to be tested + dyn_headers.overflow = 0; + } + } + } + //we could set an event on remaining data + return HTTP2FrameTypeData::SETTINGS(set); + } + Err(Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::RstStream) => { + if input.len() != HTTP2_FRAME_RSTSTREAM_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_rststream(input) { + Ok((_, rst)) => { + return HTTP2FrameTypeData::RSTSTREAM(rst); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::Priority) => { + if input.len() != HTTP2_FRAME_PRIORITY_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_priority(input) { + Ok((_, priority)) => { + return HTTP2FrameTypeData::PRIORITY(priority); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::WindowUpdate) => { + if input.len() != HTTP2_FRAME_WINDOWUPDATE_LEN { + self.set_event(HTTP2Event::InvalidFrameLength); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::Incomplete, + }); + } else { + match parser::http2_parse_frame_windowupdate(input) { + Ok((_, wu)) => { + return HTTP2FrameTypeData::WINDOWUPDATE(wu); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + } + Some(parser::HTTP2FrameType::PushPromise) => { + let dyn_headers = if dir == Direction::ToClient { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_push_promise(input, hflags, dyn_headers) { + Ok((_, hs)) => { + self.process_headers(&hs.blocks, dir); + return HTTP2FrameTypeData::PUSHPROMISE(hs); + } + Err(Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::Data) => { + return HTTP2FrameTypeData::DATA; + } + Some(parser::HTTP2FrameType::Continuation) => { + let buf = if dir == Direction::ToClient { + &mut self.s2c_buf + } else { + &mut self.c2s_buf + }; + if head.stream_id == buf.stream_id { + let max_reass = unsafe { HTTP2_MAX_REASS }; + if buf.data.len() + input.len() < max_reass { + buf.data.extend(input); + } else if buf.data.len() < max_reass { + buf.data.extend(&input[..max_reass - buf.data.len()]); + *reass_limit_reached = true; + } + if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { + let hs = parser::HTTP2FrameContinuation { + blocks: Vec::new(), + }; + return HTTP2FrameTypeData::CONTINUATION(hs); + } + } // else try to parse anyways + let input_reass = if head.stream_id == buf.stream_id { &buf.data } else { input }; + + let dyn_headers = if dir == Direction::ToClient { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_continuation(input_reass, dyn_headers) { + Ok((_, hs)) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } + self.process_headers(&hs.blocks, dir); + return HTTP2FrameTypeData::CONTINUATION(hs); + } + Err(Err::Incomplete(_)) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + if head.stream_id == buf.stream_id { + buf.stream_id = 0; + buf.data.clear(); + } + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::Headers) => { + if head.flags & parser::HTTP2_FLAG_HEADER_END_HEADERS == 0 { + let buf = if dir == Direction::ToClient { + &mut self.s2c_buf + } else { + &mut self.c2s_buf + }; + buf.data.clear(); + buf.data.extend(input); + buf.stream_id = head.stream_id; + let hs = parser::HTTP2FrameHeaders { + padlength: None, + priority: None, + blocks: Vec::new(), + }; + return HTTP2FrameTypeData::HEADERS(hs); + } + let dyn_headers = if dir == Direction::ToClient { + &mut self.dynamic_headers_tc + } else { + &mut self.dynamic_headers_ts + }; + match parser::http2_parse_frame_headers(input, hflags, dyn_headers) { + Ok((hrem, hs)) => { + self.process_headers(&hs.blocks, dir); + if !hrem.is_empty() { + SCLogDebug!("Remaining data for HTTP2 headers"); + self.set_event(HTTP2Event::ExtraHeaderData); + } + return HTTP2FrameTypeData::HEADERS(hs); + } + Err(Err::Incomplete(_)) => { + if complete { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } else { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::TooLong, + }); + } + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameData); + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::ParsingError, + }); + } + } + } + Some(parser::HTTP2FrameType::Ping) => { + return HTTP2FrameTypeData::PING; + } + _ => { + return HTTP2FrameTypeData::UNHANDLED(HTTP2FrameUnhandled { + reason: HTTP2FrameUnhandledReason::UnknownType, + }); + } + } + } + + fn parse_frames( + &mut self, mut input: &[u8], il: usize, dir: Direction, flow: *const Flow, + ) -> AppLayerResult { + while !input.is_empty() { + match parser::http2_parse_frame_header(input) { + Ok((rem, head)) => { + let hl = head.length as usize; + + //we check for completeness first + if rem.len() < hl { + //but limit ourselves so as not to exhaust memory + if hl < HTTP2_MAX_HANDLED_FRAME_SIZE { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + hl) as u32, + ); + } else if rem.len() < HTTP2_MIN_HANDLED_FRAME_SIZE { + return AppLayerResult::incomplete( + (il - input.len()) as u32, + (HTTP2_FRAME_HEADER_LEN + HTTP2_MIN_HANDLED_FRAME_SIZE) as u32, + ); + } else { + self.set_event(HTTP2Event::LongFrameData); + self.request_frame_size = head.length - (rem.len() as u32); + } + } + + //get a safe length for the buffer + let (hlsafe, complete) = if rem.len() < hl { + (rem.len(), false) + } else { + (hl, true) + }; + + if head.length == 0 && head.ftype == parser::HTTP2FrameType::Settings as u8 { + input = &rem[hlsafe..]; + continue; + } + let mut reass_limit_reached = false; + let txdata = self.parse_frame_data( + &head, + &rem[..hlsafe], + complete, + dir, + &mut reass_limit_reached, + ); + + let tx = self.find_or_create_tx(&head, &txdata, dir); + if tx.is_none() { + return AppLayerResult::err(); + } + let tx = tx.unwrap(); + if reass_limit_reached { + tx.tx_data.set_event(HTTP2Event::ReassemblyLimitReached as u8); + } + tx.handle_frame(&head, &txdata, dir); + let over = head.flags & parser::HTTP2_FLAG_HEADER_EOS != 0; + let ftype = head.ftype; + let sid = head.stream_id; + let padded = head.flags & parser::HTTP2_FLAG_HEADER_PADDED != 0; + if dir == Direction::ToServer { + tx.frames_ts.push(HTTP2Frame { + header: head, + data: txdata, + }); + } else { + tx.frames_tc.push(HTTP2Frame { + header: head, + data: txdata, + }); + } + if ftype == parser::HTTP2FrameType::Data as u8 { + match unsafe { SURICATA_HTTP2_FILE_CONFIG } { + Some(sfcm) => { + //borrow checker forbids to reuse directly tx + let index = self.find_tx_index(sid); + if index > 0 { + let tx_same = &mut self.transactions[index - 1]; + if dir == Direction::ToServer { + tx_same.ft_tc.tx_id = tx_same.tx_id - 1; + } else { + tx_same.ft_ts.tx_id = tx_same.tx_id - 1; + }; + let mut dinput = &rem[..hlsafe]; + if padded && !rem.is_empty() && usize::from(rem[0]) < hlsafe{ + dinput = &rem[1..hlsafe - usize::from(rem[0])]; + } + if tx_same.decompress( + dinput, + dir, + sfcm, + over, + flow).is_err() { + self.set_event(HTTP2Event::FailedDecompression); + } + } + } + None => panic!("no SURICATA_HTTP2_FILE_CONFIG"), + } + } + input = &rem[hlsafe..]; + } + Err(Err::Incomplete(_)) => { + //we may have consumed data from previous records + return AppLayerResult::incomplete( + (il - input.len()) as u32, + HTTP2_FRAME_HEADER_LEN as u32, + ); + } + Err(_) => { + self.set_event(HTTP2Event::InvalidFrameHeader); + return AppLayerResult::err(); + } + } + } + return AppLayerResult::ok(); + } + + fn parse_ts(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult { + //very first : skip magic + let mut magic_consumed = 0; + if self.progress < HTTP2ConnectionState::Http2StateMagicDone { + //skip magic + if input.len() >= HTTP2_MAGIC_LEN { + //skip magic + match std::str::from_utf8(&input[..HTTP2_MAGIC_LEN]) { + Ok("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") => { + input = &input[HTTP2_MAGIC_LEN..]; + magic_consumed = HTTP2_MAGIC_LEN; + } + Ok(&_) => { + self.set_event(HTTP2Event::InvalidClientMagic); + } + Err(_) => { + return AppLayerResult::err(); + } + } + self.progress = HTTP2ConnectionState::Http2StateMagicDone; + } else { + //still more buffer + return AppLayerResult::incomplete(0_u32, HTTP2_MAGIC_LEN as u32); + } + } + //first consume frame bytes + let il = input.len(); + if self.request_frame_size > 0 { + let ilen = input.len() as u32; + if self.request_frame_size >= ilen { + self.request_frame_size -= ilen; + return AppLayerResult::ok(); + } else { + let start = self.request_frame_size as usize; + input = &input[start..]; + self.request_frame_size = 0; + } + } + + //then parse all we can + let r = self.parse_frames(input, il, Direction::ToServer, flow); + if r.status == 1 { + //adds bytes consumed by banner to incomplete result + return AppLayerResult::incomplete(r.consumed + magic_consumed as u32, r.needed); + } else { + return r; + } + } + + fn parse_tc(&mut self, mut input: &[u8], flow: *const Flow) -> AppLayerResult { + //first consume frame bytes + let il = input.len(); + if self.response_frame_size > 0 { + let ilen = input.len() as u32; + if self.response_frame_size >= ilen { + self.response_frame_size -= ilen; + return AppLayerResult::ok(); + } else { + let start = self.response_frame_size as usize; + input = &input[start..]; + self.response_frame_size = 0; + } + } + //then parse all we can + return self.parse_frames(input, il, Direction::ToClient, flow); + } +} + +// C exports. + +export_tx_data_get!(rs_http2_get_tx_data, HTTP2Transaction); +export_state_data_get!(rs_http2_get_state_data, HTTP2State); + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_http2_probing_parser_tc( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + if !input.is_null() { + let slice = build_slice!(input, input_len as usize); + match parser::http2_parse_frame_header(slice) { + Ok((_, header)) => { + if header.reserved != 0 + || header.length > HTTP2_DEFAULT_MAX_FRAME_SIZE + || header.flags & 0xFE != 0 + || header.ftype != parser::HTTP2FrameType::Settings as u8 + { + return ALPROTO_FAILED; + } + return ALPROTO_HTTP2; + } + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + } + Err(_) => { + return ALPROTO_FAILED; + } + } + } + return ALPROTO_UNKNOWN; +} + +// Extern functions operating on HTTP2. +extern "C" { + pub fn HTTP2MimicHttp1Request( + orig_state: *mut std::os::raw::c_void, new_state: *mut std::os::raw::c_void, + ); +} + +// Suppress the unsafe warning here as creating a state for an app-layer +// is typically not unsafe. +#[no_mangle] +#[allow(clippy::not_unsafe_ptr_arg_deref)] +pub extern "C" fn rs_http2_state_new( + orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = HTTP2State::new(); + let boxed = Box::new(state); + let r = Box::into_raw(boxed) as *mut _; + if !orig_state.is_null() { + //we could check ALPROTO_HTTP1 == orig_proto + unsafe { + HTTP2MimicHttp1Request(orig_state, r); + } + } + return r; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_state_free(state: *mut std::os::raw::c_void) { + let mut state: Box<HTTP2State> = Box::from_raw(state as _); + state.free(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, HTTP2State); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_ts( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, HTTP2State); + let buf = stream_slice.as_slice(); + return state.parse_ts(buf, flow); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_parse_tc( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, HTTP2State); + let buf = stream_slice.as_slice(); + return state.parse_tc(buf, flow); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, HTTP2State); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, HTTP2State); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_state( + tx: *mut std::os::raw::c_void, +) -> HTTP2TransactionState { + let tx = cast_pointer!(tx, HTTP2Transaction); + return tx.state; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + return rs_http2_tx_get_state(tx) as i32; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_getfiles( + _state: *mut std::os::raw::c_void, + tx: *mut std::os::raw::c_void, direction: u8, +) -> AppLayerGetFileState { + let tx = cast_pointer!(tx, HTTP2Transaction); + if let Some(sfcm) = { SURICATA_HTTP2_FILE_CONFIG } { + if direction & STREAM_TOSERVER != 0 { + return AppLayerGetFileState { fc: &mut tx.ft_ts.file, cfg: sfcm.files_sbcfg } + } else { + return AppLayerGetFileState { fc: &mut tx.ft_tc.file, cfg: sfcm.files_sbcfg } + } + } + AppLayerGetFileState::err() +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"http2\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_register_parser() { + let default_port = CString::new("[80]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: None, // big magic string should be enough + probe_tc: Some(rs_http2_probing_parser_tc), + min_depth: HTTP2_FRAME_HEADER_LEN as u16, + max_depth: HTTP2_MAGIC_LEN as u16, + state_new: rs_http2_state_new, + state_free: rs_http2_state_free, + tx_free: rs_http2_state_tx_free, + parse_ts: rs_http2_parse_ts, + parse_tc: rs_http2_parse_tc, + get_tx_count: rs_http2_state_get_tx_count, + get_tx: rs_http2_state_get_tx, + tx_comp_st_ts: HTTP2TransactionState::HTTP2StateClosed as i32, + tx_comp_st_tc: HTTP2TransactionState::HTTP2StateClosed as i32, + tx_get_progress: rs_http2_tx_get_alstate_progress, + get_eventinfo: Some(HTTP2Event::get_event_info), + get_eventinfo_byid: Some(HTTP2Event::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: Some(rs_http2_getfiles), + get_tx_iterator: Some(applayer::state_get_tx_iterator::<HTTP2State, HTTP2Transaction>), + get_tx_data: rs_http2_get_tx_data, + get_state_data: rs_http2_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_HTTP2 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + if let Some(val) = conf_get("app-layer.protocols.http2.max-streams") { + if let Ok(v) = val.parse::<usize>() { + HTTP2_MAX_STREAMS = v; + } else { + SCLogError!("Invalid value for http2.max-streams"); + } + } + if let Some(val) = conf_get("app-layer.protocols.http2.max-table-size") { + if let Ok(v) = val.parse::<u32>() { + HTTP2_MAX_TABLESIZE = v; + } else { + SCLogError!("Invalid value for http2.max-table-size"); + } + } + if let Some(val) = conf_get("app-layer.protocols.http2.max-reassembly-size") { + if let Ok(v) = val.parse::<u32>() { + HTTP2_MAX_REASS = v as usize; + } else { + SCLogError!("Invalid value for http2.max-reassembly-size"); + } + } + SCLogDebug!("Rust http2 parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for HTTP2."); + } +} diff --git a/rust/src/http2/huffman.rs b/rust/src/http2/huffman.rs new file mode 100644 index 0000000..b03fcf3 --- /dev/null +++ b/rust/src/http2/huffman.rs @@ -0,0 +1,527 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::bits::streaming::take as take_bits; +use nom7::branch::alt; +use nom7::combinator::{complete, map_opt}; +use nom7::error::{make_error, ErrorKind}; +use nom7::{Err, IResult}; + +fn http2_huffman_table_len5(n: u32) -> Option<u8> { + match n { + 0 => Some(48), + 1 => Some(49), + 2 => Some(50), + 3 => Some(97), + 4 => Some(99), + 5 => Some(101), + 6 => Some(105), + 7 => Some(111), + 8 => Some(115), + 9 => Some(116), + _ => None, + } +} + +fn http2_decode_huffman_len5(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(5u32), http2_huffman_table_len5))(input) +} + +fn http2_huffman_table_len6(n: u32) -> Option<u8> { + match n { + 0x14 => Some(32), + 0x15 => Some(37), + 0x16 => Some(45), + 0x17 => Some(46), + 0x18 => Some(47), + 0x19 => Some(51), + 0x1a => Some(52), + 0x1b => Some(53), + 0x1c => Some(54), + 0x1d => Some(55), + 0x1e => Some(56), + 0x1f => Some(57), + 0x20 => Some(61), + 0x21 => Some(65), + 0x22 => Some(95), + 0x23 => Some(98), + 0x24 => Some(100), + 0x25 => Some(102), + 0x26 => Some(103), + 0x27 => Some(104), + 0x28 => Some(108), + 0x29 => Some(109), + 0x2a => Some(110), + 0x2b => Some(112), + 0x2c => Some(114), + 0x2d => Some(117), + _ => None, + } +} + +fn http2_decode_huffman_len6(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(6u32), http2_huffman_table_len6))(input) +} + +fn http2_huffman_table_len7(n: u32) -> Option<u8> { + match n { + 0x5c => Some(58), + 0x5d => Some(66), + 0x5e => Some(67), + 0x5f => Some(68), + 0x60 => Some(69), + 0x61 => Some(70), + 0x62 => Some(71), + 0x63 => Some(72), + 0x64 => Some(73), + 0x65 => Some(74), + 0x66 => Some(75), + 0x67 => Some(76), + 0x68 => Some(77), + 0x69 => Some(78), + 0x6a => Some(79), + 0x6b => Some(80), + 0x6c => Some(81), + 0x6d => Some(82), + 0x6e => Some(83), + 0x6f => Some(84), + 0x70 => Some(85), + 0x71 => Some(86), + 0x72 => Some(87), + 0x73 => Some(89), + 0x74 => Some(106), + 0x75 => Some(107), + 0x76 => Some(113), + 0x77 => Some(118), + 0x78 => Some(119), + 0x79 => Some(120), + 0x7a => Some(121), + 0x7b => Some(122), + _ => None, + } +} + +fn http2_decode_huffman_len7(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(7u32), http2_huffman_table_len7))(input) +} + +fn http2_huffman_table_len8(n: u32) -> Option<u8> { + match n { + 0xf8 => Some(38), + 0xf9 => Some(42), + 0xfa => Some(44), + 0xfb => Some(59), + 0xfc => Some(88), + 0xfd => Some(90), + _ => None, + } +} + +fn http2_decode_huffman_len8(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(8u32), http2_huffman_table_len8))(input) +} + +fn http2_huffman_table_len10(n: u32) -> Option<u8> { + match n { + 0x3f8 => Some(33), + 0x3f9 => Some(34), + 0x3fa => Some(40), + 0x3fb => Some(41), + 0x3fc => Some(63), + _ => None, + } +} + +fn http2_decode_huffman_len10(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(10u32), http2_huffman_table_len10))(input) +} + +fn http2_huffman_table_len11(n: u32) -> Option<u8> { + match n { + 0x7fa => Some(39), + 0x7fb => Some(43), + 0x7fc => Some(124), + _ => None, + } +} + +fn http2_decode_huffman_len11(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(11u32), http2_huffman_table_len11))(input) +} + +fn http2_huffman_table_len12(n: u32) -> Option<u8> { + match n { + 0xffa => Some(35), + 0xffb => Some(62), + _ => None, + } +} + +fn http2_decode_huffman_len12(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(12u32), http2_huffman_table_len12))(input) +} + +fn http2_huffman_table_len13(n: u32) -> Option<u8> { + match n { + 0x1ff8 => Some(0), + 0x1ff9 => Some(36), + 0x1ffa => Some(64), + 0x1ffb => Some(91), + 0x1ffc => Some(93), + 0x1ffd => Some(126), + _ => None, + } +} + +fn http2_decode_huffman_len13(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(13u32), http2_huffman_table_len13))(input) +} + +fn http2_huffman_table_len14(n: u32) -> Option<u8> { + match n { + 0x3ffc => Some(94), + 0x3ffd => Some(125), + _ => None, + } +} + +fn http2_decode_huffman_len14(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(14u32), http2_huffman_table_len14))(input) +} + +fn http2_huffman_table_len15(n: u32) -> Option<u8> { + match n { + 0x7ffc => Some(60), + 0x7ffd => Some(96), + 0x7ffe => Some(123), + _ => None, + } +} + +fn http2_decode_huffman_len15(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(15u32), http2_huffman_table_len15))(input) +} + +fn http2_huffman_table_len19(n: u32) -> Option<u8> { + match n { + 0x7fff0 => Some(92), + 0x7fff1 => Some(195), + 0x7fff2 => Some(208), + _ => None, + } +} + +fn http2_decode_huffman_len19(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(19u32), http2_huffman_table_len19))(input) +} + +fn http2_huffman_table_len20(n: u32) -> Option<u8> { + match n { + 0xfffe6 => Some(128), + 0xfffe7 => Some(130), + 0xfffe8 => Some(131), + 0xfffe9 => Some(162), + 0xfffea => Some(184), + 0xfffeb => Some(194), + 0xfffec => Some(224), + 0xfffed => Some(226), + _ => None, + } +} + +fn http2_decode_huffman_len20(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(20u32), http2_huffman_table_len20))(input) +} + +fn http2_huffman_table_len21(n: u32) -> Option<u8> { + match n { + 0x1fffdc => Some(153), + 0x1fffdd => Some(161), + 0x1fffde => Some(167), + 0x1fffdf => Some(172), + 0x1fffe0 => Some(176), + 0x1fffe1 => Some(177), + 0x1fffe2 => Some(179), + 0x1fffe3 => Some(209), + 0x1fffe4 => Some(216), + 0x1fffe5 => Some(217), + 0x1fffe6 => Some(227), + 0x1fffe7 => Some(229), + 0x1fffe8 => Some(230), + _ => None, + } +} + +fn http2_decode_huffman_len21(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(21u32), http2_huffman_table_len21))(input) +} + +fn http2_huffman_table_len22(n: u32) -> Option<u8> { + match n { + 0x3fffd2 => Some(129), + 0x3fffd3 => Some(132), + 0x3fffd4 => Some(133), + 0x3fffd5 => Some(134), + 0x3fffd6 => Some(136), + 0x3fffd7 => Some(146), + 0x3fffd8 => Some(154), + 0x3fffd9 => Some(156), + 0x3fffda => Some(160), + 0x3fffdb => Some(163), + 0x3fffdc => Some(164), + 0x3fffdd => Some(169), + 0x3fffde => Some(170), + 0x3fffdf => Some(173), + 0x3fffe0 => Some(178), + 0x3fffe1 => Some(181), + 0x3fffe2 => Some(185), + 0x3fffe3 => Some(186), + 0x3fffe4 => Some(187), + 0x3fffe5 => Some(189), + 0x3fffe6 => Some(190), + 0x3fffe7 => Some(196), + 0x3fffe8 => Some(198), + 0x3fffe9 => Some(228), + 0x3fffea => Some(232), + 0x3fffeb => Some(233), + _ => None, + } +} + +fn http2_decode_huffman_len22(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(22u32), http2_huffman_table_len22))(input) +} + +fn http2_huffman_table_len23(n: u32) -> Option<u8> { + match n { + 0x7fffd8 => Some(1), + 0x7fffd9 => Some(135), + 0x7fffda => Some(137), + 0x7fffdb => Some(138), + 0x7fffdc => Some(139), + 0x7fffdd => Some(140), + 0x7fffde => Some(141), + 0x7fffdf => Some(143), + 0x7fffe0 => Some(147), + 0x7fffe1 => Some(149), + 0x7fffe2 => Some(150), + 0x7fffe3 => Some(151), + 0x7fffe4 => Some(152), + 0x7fffe5 => Some(155), + 0x7fffe6 => Some(157), + 0x7fffe7 => Some(158), + 0x7fffe8 => Some(165), + 0x7fffe9 => Some(166), + 0x7fffea => Some(168), + 0x7fffeb => Some(174), + 0x7fffec => Some(175), + 0x7fffed => Some(180), + 0x7fffee => Some(182), + 0x7fffef => Some(183), + 0x7ffff0 => Some(188), + 0x7ffff1 => Some(191), + 0x7ffff2 => Some(197), + 0x7ffff3 => Some(231), + 0x7ffff4 => Some(239), + _ => None, + } +} + +fn http2_decode_huffman_len23(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(23u32), http2_huffman_table_len23))(input) +} + +fn http2_huffman_table_len24(n: u32) -> Option<u8> { + match n { + 0xffffea => Some(9), + 0xffffeb => Some(142), + 0xffffec => Some(144), + 0xffffed => Some(145), + 0xffffee => Some(148), + 0xffffef => Some(159), + 0xfffff0 => Some(171), + 0xfffff1 => Some(206), + 0xfffff2 => Some(215), + 0xfffff3 => Some(225), + 0xfffff4 => Some(236), + 0xfffff5 => Some(237), + _ => None, + } +} + +fn http2_decode_huffman_len24(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(24u32), http2_huffman_table_len24))(input) +} + +fn http2_huffman_table_len25(n: u32) -> Option<u8> { + match n { + 0x1ffffec => Some(199), + 0x1ffffed => Some(207), + 0x1ffffee => Some(234), + 0x1ffffef => Some(235), + _ => None, + } +} + +fn http2_decode_huffman_len25(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(25u32), http2_huffman_table_len25))(input) +} + +fn http2_huffman_table_len26(n: u32) -> Option<u8> { + match n { + 0x3ffffe0 => Some(192), + 0x3ffffe1 => Some(193), + 0x3ffffe2 => Some(200), + 0x3ffffe3 => Some(201), + 0x3ffffe4 => Some(202), + 0x3ffffe5 => Some(205), + 0x3ffffe6 => Some(210), + 0x3ffffe7 => Some(213), + 0x3ffffe8 => Some(218), + 0x3ffffe9 => Some(219), + 0x3ffffea => Some(238), + 0x3ffffeb => Some(240), + 0x3ffffec => Some(242), + 0x3ffffed => Some(243), + 0x3ffffee => Some(255), + _ => None, + } +} + +fn http2_decode_huffman_len26((i, bit_offset): (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(26u32), http2_huffman_table_len26))((i, bit_offset)) +} + +fn http2_huffman_table_len27(n: u32) -> Option<u8> { + match n { + 0x7ffffde => Some(203), + 0x7ffffdf => Some(204), + 0x7ffffe0 => Some(211), + 0x7ffffe1 => Some(212), + 0x7ffffe2 => Some(214), + 0x7ffffe3 => Some(221), + 0x7ffffe4 => Some(222), + 0x7ffffe5 => Some(223), + 0x7ffffe6 => Some(241), + 0x7ffffe7 => Some(244), + 0x7ffffe8 => Some(245), + 0x7ffffe9 => Some(246), + 0x7ffffea => Some(247), + 0x7ffffeb => Some(248), + 0x7ffffec => Some(250), + 0x7ffffed => Some(251), + 0x7ffffee => Some(252), + 0x7ffffef => Some(253), + 0x7fffff0 => Some(254), + _ => None, + } +} + +fn http2_decode_huffman_len27(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(27u32), http2_huffman_table_len27))(input) +} + +fn http2_huffman_table_len28(n: u32) -> Option<u8> { + match n { + 0xfffffe2 => Some(2), + 0xfffffe3 => Some(3), + 0xfffffe4 => Some(4), + 0xfffffe5 => Some(5), + 0xfffffe6 => Some(6), + 0xfffffe7 => Some(7), + 0xfffffe8 => Some(8), + 0xfffffe9 => Some(11), + 0xfffffea => Some(12), + 0xfffffeb => Some(14), + 0xfffffec => Some(15), + 0xfffffed => Some(16), + 0xfffffee => Some(17), + 0xfffffef => Some(18), + 0xffffff0 => Some(19), + 0xffffff1 => Some(20), + 0xffffff2 => Some(21), + 0xffffff3 => Some(23), + 0xffffff4 => Some(24), + 0xffffff5 => Some(25), + 0xffffff6 => Some(26), + 0xffffff7 => Some(27), + 0xffffff8 => Some(28), + 0xffffff9 => Some(29), + 0xffffffa => Some(30), + 0xffffffb => Some(31), + 0xffffffc => Some(127), + 0xffffffd => Some(220), + 0xffffffe => Some(249), + _ => None, + } +} + +fn http2_decode_huffman_len28(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(28u32), http2_huffman_table_len28))(input) +} + +fn http2_huffman_table_len30(n: u32) -> Option<u8> { + match n { + 0x3ffffffc => Some(10), + 0x3ffffffd => Some(13), + 0x3ffffffe => Some(22), + // 0x3fffffff => Some(256), + _ => None, + } +} + +fn http2_decode_huffman_len30(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + complete(map_opt(take_bits(30u32), http2_huffman_table_len30))(input) +} + +//hack to end many0 even if some bits are remaining +fn http2_decode_huffman_end(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + return Err(Err::Error(make_error(input, ErrorKind::Eof))); +} + +//we could profile and optimize performance here +pub fn http2_decode_huffman(input: (&[u8], usize)) -> IResult<(&[u8], usize), u8> { + // trait nom::branch::Alt is implemented for lists up to size 20, + // so use nested `alt` as a workaround (see nom documentation for `alt`) + alt(( + http2_decode_huffman_len5, + http2_decode_huffman_len6, + http2_decode_huffman_len7, + http2_decode_huffman_len8, + http2_decode_huffman_len10, + http2_decode_huffman_len11, + http2_decode_huffman_len12, + http2_decode_huffman_len13, + http2_decode_huffman_len14, + http2_decode_huffman_len15, + http2_decode_huffman_len19, + http2_decode_huffman_len20, + http2_decode_huffman_len21, + http2_decode_huffman_len21, + http2_decode_huffman_len22, + http2_decode_huffman_len23, + http2_decode_huffman_len24, + http2_decode_huffman_len25, + http2_decode_huffman_len26, + http2_decode_huffman_len27, + alt(( + http2_decode_huffman_len28, + http2_decode_huffman_len30, + http2_decode_huffman_end, + )), + ))(input) +} diff --git a/rust/src/http2/logger.rs b/rust/src/http2/logger.rs new file mode 100644 index 0000000..d25f852 --- /dev/null +++ b/rust/src/http2/logger.rs @@ -0,0 +1,279 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::http2::{HTTP2Frame, HTTP2FrameTypeData, HTTP2Transaction}; +use super::parser; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; +use std::collections::HashMap; + +#[derive(Hash, PartialEq, Eq, Debug)] +enum HeaderName { + Method, + Path, + Host, + UserAgent, + Status, + ContentLength, +} + +fn log_http2_headers<'a>( + blocks: &'a [parser::HTTP2FrameHeaderBlock], js: &mut JsonBuilder, + common: &mut HashMap<HeaderName, &'a Vec<u8>>, +) -> Result<(), JsonError> { + for block in blocks { + js.start_object()?; + match block.error { + parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess => { + js.set_string_from_bytes("name", &block.name)?; + js.set_string_from_bytes("value", &block.value)?; + if let Ok(name) = std::str::from_utf8(&block.name) { + match name.to_lowercase().as_ref() { + ":method" => { + common.insert(HeaderName::Method, &block.value); + } + ":path" => { + common.insert(HeaderName::Path, &block.value); + } + ":status" => { + common.insert(HeaderName::Status, &block.value); + } + "user-agent" => { + common.insert(HeaderName::UserAgent, &block.value); + } + "host" => { + common.insert(HeaderName::Host, &block.value); + } + "content-length" => { + common.insert(HeaderName::ContentLength, &block.value); + } + _ => {} + } + } + } + parser::HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate => { + js.set_uint("table_size_update", block.sizeupdate)?; + } + _ => { + js.set_string("error", &block.error.to_string())?; + } + } + js.close()?; + } + return Ok(()); +} + +fn log_headers<'a>( + frames: &'a Vec<HTTP2Frame>, js: &mut JsonBuilder, + common: &mut HashMap<HeaderName, &'a Vec<u8>>, +) -> Result<bool, JsonError> { + let mut has_headers = false; + for frame in frames { + match &frame.data { + HTTP2FrameTypeData::HEADERS(hd) => { + log_http2_headers(&hd.blocks, js, common)?; + has_headers = true; + } + HTTP2FrameTypeData::PUSHPROMISE(hd) => { + log_http2_headers(&hd.blocks, js, common)?; + has_headers = true; + } + HTTP2FrameTypeData::CONTINUATION(hd) => { + log_http2_headers(&hd.blocks, js, common)?; + has_headers = true; + } + _ => {} + } + } + Ok(has_headers) +} + +fn log_http2_frames(frames: &[HTTP2Frame], js: &mut JsonBuilder) -> Result<bool, JsonError> { + let mut has_settings = false; + for frame in frames { + if let HTTP2FrameTypeData::SETTINGS(set) = &frame.data { + if !has_settings { + js.open_array("settings")?; + has_settings = true; + } + for e in set { + js.start_object()?; + js.set_string("settings_id", &format!("SETTINGS{}", &e.id.to_string().to_uppercase()))?; + js.set_uint("settings_value", e.value as u64)?; + js.close()?; + } + } + } + if has_settings { + js.close()?; + } + + let mut has_error_code = false; + let mut has_priority = false; + let mut has_multiple = false; + for frame in frames { + match &frame.data { + HTTP2FrameTypeData::GOAWAY(goaway) => { + if !has_error_code { + let errcode: Option<parser::HTTP2ErrorCode> = + num::FromPrimitive::from_u32(goaway.errorcode); + match errcode { + Some(errstr) => { + js.set_string("error_code", &errstr.to_string().to_uppercase())?; + } + None => { + //use uint32 + js.set_string("error_code", &goaway.errorcode.to_string())?; + } + } + has_error_code = true; + } else if !has_multiple { + js.set_string("has_multiple", "error_code")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::RSTSTREAM(rst) => { + if !has_error_code { + let errcode: Option<parser::HTTP2ErrorCode> = + num::FromPrimitive::from_u32(rst.errorcode); + match errcode { + Some(errstr) => { + js.set_string("error_code", &errstr.to_string())?; + } + None => { + //use uint32 + js.set_string("error_code", &rst.errorcode.to_string())?; + } + } + has_error_code = true; + } else if !has_multiple { + js.set_string("has_multiple", "error_code")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::PRIORITY(priority) => { + if !has_priority { + js.set_uint("priority", priority.weight as u64)?; + has_priority = true; + } else if !has_multiple { + js.set_string("has_multiple", "priority")?; + has_multiple = true; + } + } + HTTP2FrameTypeData::HEADERS(hd) => { + if let Some(ref priority) = hd.priority { + if !has_priority { + js.set_uint("priority", priority.weight as u64)?; + has_priority = true; + } else if !has_multiple { + js.set_string("has_multiple", "priority")?; + has_multiple = true; + } + } + } + _ => {} + } + } + return Ok(has_settings || has_error_code || has_priority); +} + +fn log_http2(tx: &HTTP2Transaction, js: &mut JsonBuilder) -> Result<bool, JsonError> { + js.set_string("version", "2")?; + + let mut common: HashMap<HeaderName, &Vec<u8>> = HashMap::new(); + + let mut has_headers = false; + + // Request headers. + let mark = js.get_mark(); + js.open_array("request_headers")?; + if log_headers(&tx.frames_ts, js, &mut common)? { + js.close()?; + has_headers = true; + } else { + js.restore_mark(&mark)?; + } + + // Response headers. + let mark = js.get_mark(); + js.open_array("response_headers")?; + if log_headers(&tx.frames_tc, js, &mut common)? { + js.close()?; + has_headers = true; + } else { + js.restore_mark(&mark)?; + } + + for (name, value) in common { + match name { + HeaderName::Method => { + js.set_string_from_bytes("http_method", value)?; + } + HeaderName::Path => { + js.set_string_from_bytes("url", value)?; + } + HeaderName::Host => { + js.set_string_from_bytes("hostname", value)?; + } + HeaderName::UserAgent => { + js.set_string_from_bytes("http_user_agent", value)?; + } + HeaderName::ContentLength => { + if let Ok(value) = std::str::from_utf8(value) { + if let Ok(value) = value.parse::<u64>() { + js.set_uint("length", value)?; + } + } + } + HeaderName::Status => { + if let Ok(value) = std::str::from_utf8(value) { + if let Ok(value) = value.parse::<u64>() { + js.set_uint("status", value)?; + } + } + } + } + } + + // The rest of http2 logging is placed in an "http2" object. + js.open_object("http2")?; + + js.set_uint("stream_id", tx.stream_id as u64)?; + js.open_object("request")?; + let has_request = log_http2_frames(&tx.frames_ts, js)?; + js.close()?; + + js.open_object("response")?; + let has_response = log_http2_frames(&tx.frames_tc, js)?; + js.close()?; + + // Close http2. + js.close()?; + + return Ok(has_request || has_response || has_headers); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http2_log_json( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, HTTP2Transaction); + if let Ok(x) = log_http2(tx, js) { + return x; + } + return false; +} diff --git a/rust/src/http2/mod.rs b/rust/src/http2/mod.rs new file mode 100644 index 0000000..910e968 --- /dev/null +++ b/rust/src/http2/mod.rs @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! HTTP/2 parser, detection, logger and application layer module. + +#![allow(clippy::result_unit_err)] + +mod decompression; +pub mod detect; +pub mod http2; +mod huffman; +pub mod logger; +mod parser; +mod range; diff --git a/rust/src/http2/parser.rs b/rust/src/http2/parser.rs new file mode 100644 index 0000000..adabeb2 --- /dev/null +++ b/rust/src/http2/parser.rs @@ -0,0 +1,1050 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::huffman; +use crate::common::nom7::bits; +use crate::detect::uint::{detect_parse_uint, DetectUintData}; +use crate::http2::http2::{HTTP2DynTable, HTTP2_MAX_TABLESIZE}; +use nom7::bits::streaming::take as take_bits; +use nom7::branch::alt; +use nom7::bytes::streaming::{is_a, is_not, take, take_while}; +use nom7::combinator::{complete, cond, map_opt, opt, rest, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::many0; +use nom7::number::streaming::{be_u16, be_u24, be_u32, be_u8}; +use nom7::sequence::tuple; +use nom7::{Err, IResult}; +use std::fmt; +use std::str::FromStr; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2FrameType { + Data = 0, + Headers = 1, + Priority = 2, + RstStream = 3, + Settings = 4, + PushPromise = 5, + Ping = 6, + GoAway = 7, + WindowUpdate = 8, + Continuation = 9, +} + +impl fmt::Display for HTTP2FrameType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2FrameType { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "DATA" => Ok(HTTP2FrameType::Data), + "HEADERS" => Ok(HTTP2FrameType::Headers), + "PRIORITY" => Ok(HTTP2FrameType::Priority), + "RSTSTREAM" => Ok(HTTP2FrameType::RstStream), + "SETTINGS" => Ok(HTTP2FrameType::Settings), + "PUSHPROMISE" => Ok(HTTP2FrameType::PushPromise), + "PING" => Ok(HTTP2FrameType::Ping), + "GOAWAY" => Ok(HTTP2FrameType::GoAway), + "WINDOWUPDATE" => Ok(HTTP2FrameType::WindowUpdate), + "CONTINUATION" => Ok(HTTP2FrameType::Continuation), + _ => Err(format!("'{}' is not a valid value for HTTP2FrameType", s)), + } + } +} + +#[derive(PartialEq, Eq, Debug)] +pub struct HTTP2FrameHeader { + //we could add detection on (GOAWAY) additional data + pub length: u32, + pub ftype: u8, + pub flags: u8, + pub reserved: u8, + pub stream_id: u32, +} + +pub fn http2_parse_frame_header(i: &[u8]) -> IResult<&[u8], HTTP2FrameHeader> { + let (i, length) = be_u24(i)?; + let (i, ftype) = be_u8(i)?; + let (i, flags) = be_u8(i)?; + let (i, b) = be_u32(i)?; + let (reserved, stream_id) = ((b >> 31) as u8, b & 0x7fff_ffff); + Ok(( + i, + HTTP2FrameHeader { + length, + ftype, + flags, + reserved, + stream_id, + }, + )) +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2ErrorCode { + NoError = 0, + ProtocolError = 1, + InternalError = 2, + FlowControlError = 3, + SettingsTimeout = 4, + StreamClosed = 5, + FrameSizeError = 6, + RefusedStream = 7, + Cancel = 8, + CompressionError = 9, + ConnectError = 10, + EnhanceYourCalm = 11, + InadequateSecurity = 12, + Http11Required = 13, +} + +impl fmt::Display for HTTP2ErrorCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2ErrorCode { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "NO_ERROR" => Ok(HTTP2ErrorCode::NoError), + "PROTOCOL_ERROR" => Ok(HTTP2ErrorCode::ProtocolError), + "FLOW_CONTROL_ERROR" => Ok(HTTP2ErrorCode::FlowControlError), + "SETTINGS_TIMEOUT" => Ok(HTTP2ErrorCode::SettingsTimeout), + "STREAM_CLOSED" => Ok(HTTP2ErrorCode::StreamClosed), + "FRAME_SIZE_ERROR" => Ok(HTTP2ErrorCode::FrameSizeError), + "REFUSED_STREAM" => Ok(HTTP2ErrorCode::RefusedStream), + "CANCEL" => Ok(HTTP2ErrorCode::Cancel), + "COMPRESSION_ERROR" => Ok(HTTP2ErrorCode::CompressionError), + "CONNECT_ERROR" => Ok(HTTP2ErrorCode::ConnectError), + "ENHANCE_YOUR_CALM" => Ok(HTTP2ErrorCode::EnhanceYourCalm), + "INADEQUATE_SECURITY" => Ok(HTTP2ErrorCode::InadequateSecurity), + "HTTP_1_1_REQUIRED" => Ok(HTTP2ErrorCode::Http11Required), + _ => Err(format!("'{}' is not a valid value for HTTP2ErrorCode", s)), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameGoAway { + pub errorcode: u32, //HTTP2ErrorCode +} + +pub fn http2_parse_frame_goaway(i: &[u8]) -> IResult<&[u8], HTTP2FrameGoAway> { + let (i, errorcode) = be_u32(i)?; + Ok((i, HTTP2FrameGoAway { errorcode })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameRstStream { + pub errorcode: u32, ////HTTP2ErrorCode +} + +pub fn http2_parse_frame_rststream(i: &[u8]) -> IResult<&[u8], HTTP2FrameRstStream> { + let (i, errorcode) = be_u32(i)?; + Ok((i, HTTP2FrameRstStream { errorcode })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FramePriority { + pub exclusive: u8, + pub dependency: u32, + pub weight: u8, +} + +pub fn http2_parse_frame_priority(i: &[u8]) -> IResult<&[u8], HTTP2FramePriority> { + let (i, b) = be_u32(i)?; + let (exclusive, dependency) = ((b >> 31) as u8, b & 0x7fff_ffff); + let (i, weight) = be_u8(i)?; + Ok(( + i, + HTTP2FramePriority { + exclusive, + dependency, + weight, + }, + )) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameWindowUpdate { + pub reserved: u8, + pub sizeinc: u32, +} + +pub fn http2_parse_frame_windowupdate(i: &[u8]) -> IResult<&[u8], HTTP2FrameWindowUpdate> { + let (i, b) = be_u32(i)?; + let (reserved, sizeinc) = ((b >> 31) as u8, b & 0x7fff_ffff); + Ok((i, HTTP2FrameWindowUpdate { reserved, sizeinc })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameHeadersPriority { + pub exclusive: u8, + pub dependency: u32, + pub weight: u8, +} + +pub fn http2_parse_headers_priority(i: &[u8]) -> IResult<&[u8], HTTP2FrameHeadersPriority> { + let (i, b) = be_u32(i)?; + let (exclusive, dependency) = ((b >> 31) as u8, b & 0x7fff_ffff); + let (i, weight) = be_u8(i)?; + Ok(( + i, + HTTP2FrameHeadersPriority { + exclusive, + dependency, + weight, + }, + )) +} + +pub const HTTP2_STATIC_HEADERS_NUMBER: usize = 61; + +fn http2_frame_header_static(n: u64, dyn_headers: &HTTP2DynTable) -> Option<HTTP2FrameHeaderBlock> { + let (name, value) = match n { + 1 => (":authority", ""), + 2 => (":method", "GET"), + 3 => (":method", "POST"), + 4 => (":path", "/"), + 5 => (":path", "/index.html"), + 6 => (":scheme", "http"), + 7 => (":scheme", "https"), + 8 => (":status", "200"), + 9 => (":status", "204"), + 10 => (":status", "206"), + 11 => (":status", "304"), + 12 => (":status", "400"), + 13 => (":status", "404"), + 14 => (":status", "500"), + 15 => ("accept-charset", ""), + 16 => ("accept-encoding", "gzip, deflate"), + 17 => ("accept-language", ""), + 18 => ("accept-ranges", ""), + 19 => ("accept", ""), + 20 => ("access-control-allow-origin", ""), + 21 => ("age", ""), + 22 => ("allow", ""), + 23 => ("authorization", ""), + 24 => ("cache-control", ""), + 25 => ("content-disposition", ""), + 26 => ("content-encoding", ""), + 27 => ("content-language", ""), + 28 => ("content-length", ""), + 29 => ("content-location", ""), + 30 => ("content-range", ""), + 31 => ("content-type", ""), + 32 => ("cookie", ""), + 33 => ("date", ""), + 34 => ("etag", ""), + 35 => ("expect", ""), + 36 => ("expires", ""), + 37 => ("from", ""), + 38 => ("host", ""), + 39 => ("if-match", ""), + 40 => ("if-modified-since", ""), + 41 => ("if-none-match", ""), + 42 => ("if-range", ""), + 43 => ("if-unmodified-since", ""), + 44 => ("last-modified", ""), + 45 => ("link", ""), + 46 => ("location", ""), + 47 => ("max-forwards", ""), + 48 => ("proxy-authenticate", ""), + 49 => ("proxy-authorization", ""), + 50 => ("range", ""), + 51 => ("referer", ""), + 52 => ("refresh", ""), + 53 => ("retry-after", ""), + 54 => ("server", ""), + 55 => ("set-cookie", ""), + 56 => ("strict-transport-security", ""), + 57 => ("transfer-encoding", ""), + 58 => ("user-agent", ""), + 59 => ("vary", ""), + 60 => ("via", ""), + 61 => ("www-authenticate", ""), + _ => ("", ""), + }; + if !name.is_empty() { + return Some(HTTP2FrameHeaderBlock { + name: name.as_bytes().to_vec(), + value: value.as_bytes().to_vec(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }); + } else { + //use dynamic table + if n == 0 { + return Some(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0, + sizeupdate: 0, + }); + } else if dyn_headers.table.len() + HTTP2_STATIC_HEADERS_NUMBER < n as usize { + return Some(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + sizeupdate: 0, + }); + } else { + let indyn = dyn_headers.table.len() - (n as usize - HTTP2_STATIC_HEADERS_NUMBER); + let headcopy = HTTP2FrameHeaderBlock { + name: dyn_headers.table[indyn].name.to_vec(), + value: dyn_headers.table[indyn].value.to_vec(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + sizeupdate: 0, + }; + return Some(headcopy); + } + } +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum HTTP2HeaderDecodeStatus { + HTTP2HeaderDecodeSuccess = 0, + HTTP2HeaderDecodeSizeUpdate = 1, + HTTP2HeaderDecodeError = 0x80, + HTTP2HeaderDecodeNotIndexed = 0x81, + HTTP2HeaderDecodeIntegerOverflow = 0x82, + HTTP2HeaderDecodeIndex0 = 0x83, +} + +impl fmt::Display for HTTP2HeaderDecodeStatus { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameHeaderBlock { + pub name: Vec<u8>, + pub value: Vec<u8>, + pub error: HTTP2HeaderDecodeStatus, + pub sizeupdate: u64, +} + +fn http2_parse_headers_block_indexed<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(1u8), |&x| x == 1), + take_bits(7u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0x7F)?; + match http2_frame_header_static(indexreal, dyn_headers) { + Some(h) => Ok((i3, h)), + _ => Err(Err::Error(make_error(i3, ErrorKind::MapOpt))), + } +} + +fn http2_parse_headers_block_string(input: &[u8]) -> IResult<&[u8], Vec<u8>> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(tuple((take_bits(1u8), take_bits(7u8))))(input) + } + let (i1, huffslen) = parser(input)?; + let (i2, stringlen) = http2_parse_var_uint(i1, huffslen.1 as u64, 0x7F)?; + let (i3, data) = take(stringlen as usize)(i2)?; + if huffslen.0 == 0 { + return Ok((i3, data.to_vec())); + } else { + let (_, val) = bits(many0(huffman::http2_decode_huffman))(data)?; + return Ok((i3, val)); + } +} + +fn http2_parse_headers_block_literal_common<'a>( + input: &'a [u8], index: u64, dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + let (i3, name, error) = if index == 0 { + match http2_parse_headers_block_string(input) { + Ok((r, n)) => Ok((r, n, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess)), + Err(e) => Err(e), + } + } else { + match http2_frame_header_static(index, dyn_headers) { + Some(x) => Ok(( + input, + x.name, + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess, + )), + None => Ok(( + input, + Vec::new(), + HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeNotIndexed, + )), + } + }?; + let (i4, value) = http2_parse_headers_block_string(i3)?; + return Ok(( + i4, + HTTP2FrameHeaderBlock { + name, + value, + error, + sizeupdate: 0, + }, + )); +} + +fn http2_parse_headers_block_literal_incindex<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(2u8), |&x| x == 1), + take_bits(6u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0x3F)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + match r { + Ok((r, head)) => { + let headcopy = HTTP2FrameHeaderBlock { + name: head.name.to_vec(), + value: head.value.to_vec(), + error: head.error, + sizeupdate: 0, + }; + if head.error == HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSuccess { + dyn_headers.current_size += 32 + headcopy.name.len() + headcopy.value.len(); + //in case of overflow, best effort is to keep first headers + if dyn_headers.overflow > 0 { + if dyn_headers.overflow == 1 { + if dyn_headers.current_size <= (unsafe { HTTP2_MAX_TABLESIZE } as usize) { + //overflow had not yet happened + dyn_headers.table.push(headcopy); + } else if dyn_headers.current_size > dyn_headers.max_size { + //overflow happens, we cannot replace evicted headers + dyn_headers.overflow = 2; + } + } + } else { + dyn_headers.table.push(headcopy); + } + let mut toremove = 0; + while dyn_headers.current_size > dyn_headers.max_size + && toremove < dyn_headers.table.len() + { + dyn_headers.current_size -= + 32 + dyn_headers.table[toremove].name.len() + dyn_headers.table[toremove].value.len(); + toremove += 1; + } + dyn_headers.table.drain(0..toremove); + } + return Ok((r, head)); + } + Err(e) => { + return Err(e); + } + } +} + +fn http2_parse_headers_block_literal_noindex<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(4u8), |&x| x == 0), + take_bits(4u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0xF)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_headers_block_literal_neverindex<'a>( + input: &'a [u8], dyn_headers: &HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(4u8), |&x| x == 1), + take_bits(4u8), + ))))(input) + } + let (i2, indexed) = parser(input)?; + let (i3, indexreal) = http2_parse_var_uint(i2, indexed.1 as u64, 0xF)?; + let r = http2_parse_headers_block_literal_common(i3, indexreal, dyn_headers); + return r; +} + +fn http2_parse_var_uint(input: &[u8], value: u64, max: u64) -> IResult<&[u8], u64> { + if value < max { + return Ok((input, value)); + } + let (i2, varia) = take_while(|ch| (ch & 0x80) != 0)(input)?; + let (i3, finalv) = be_u8(i2)?; + if varia.len() > 9 || (varia.len() == 9 && finalv > 1) { + // this will overflow u64 + return Ok((i3, 0)); + } + let mut varval = max; + for (i, e) in varia.iter().enumerate() { + varval += ((e & 0x7F) as u64) << (7 * i); + } + match varval.checked_add((finalv as u64) << (7 * varia.len())) { + None => { + return Err(Err::Error(make_error(i3, ErrorKind::LengthValue))); + } + Some(x) => { + return Ok((i3, x)); + } + } +} + +fn http2_parse_headers_block_dynamic_size<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + fn parser(input: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits(complete(tuple(( + verify(take_bits(3u8), |&x| x == 1), + take_bits(5u8), + ))))(input) + } + let (i2, maxsize) = parser(input)?; + let (i3, maxsize2) = http2_parse_var_uint(i2, maxsize.1 as u64, 0x1F)?; + if (maxsize2 as usize) < dyn_headers.max_size { + //dyn_headers.max_size is updated later with all headers + //may evict entries + let mut toremove = 0; + while dyn_headers.current_size > (maxsize2 as usize) && toremove < dyn_headers.table.len() { + // we check dyn_headers.table as we may be in best effort + // because the previous maxsize was too big for us to retain all the headers + dyn_headers.current_size -= 32 + + dyn_headers.table[toremove].name.len() + + dyn_headers.table[toremove].value.len(); + toremove += 1; + } + dyn_headers.table.drain(0..toremove); + } + return Ok(( + i3, + HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeSizeUpdate, + sizeupdate: maxsize2, + }, + )); +} + +fn http2_parse_headers_block<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaderBlock> { + //caller guarantees o have at least one byte + if input[0] & 0x80 != 0 { + return http2_parse_headers_block_indexed(input, dyn_headers); + } else if input[0] & 0x40 != 0 { + return http2_parse_headers_block_literal_incindex(input, dyn_headers); + } else if input[0] & 0x20 != 0 { + return http2_parse_headers_block_dynamic_size(input, dyn_headers); + } else if input[0] & 0x10 != 0 { + return http2_parse_headers_block_literal_neverindex(input, dyn_headers); + } else { + return http2_parse_headers_block_literal_noindex(input, dyn_headers); + } +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameHeaders { + pub padlength: Option<u8>, + pub priority: Option<HTTP2FrameHeadersPriority>, + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +//end stream +pub const HTTP2_FLAG_HEADER_EOS: u8 = 0x1; +pub const HTTP2_FLAG_HEADER_END_HEADERS: u8 = 0x4; +pub const HTTP2_FLAG_HEADER_PADDED: u8 = 0x8; +const HTTP2_FLAG_HEADER_PRIORITY: u8 = 0x20; + +fn http2_parse_headers_blocks<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], Vec<HTTP2FrameHeaderBlock>> { + let mut blocks = Vec::new(); + let mut i3 = input; + while !i3.is_empty() { + match http2_parse_headers_block(i3, dyn_headers) { + Ok((rem, b)) => { + blocks.push(b); + debug_validate_bug_on!(i3.len() == rem.len()); + if i3.len() == rem.len() { + //infinite loop + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + i3 = rem; + } + Err(Err::Error(ref err)) => { + // if we error from http2_parse_var_uint, we keep the first parsed headers + if err.code == ErrorKind::LengthValue { + blocks.push(HTTP2FrameHeaderBlock { + name: Vec::new(), + value: Vec::new(), + error: HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIntegerOverflow, + sizeupdate: 0, + }); + break; + } + } + Err(x) => { + return Err(x); + } + } + } + return Ok((i3, blocks)); +} + +pub fn http2_parse_frame_headers<'a>( + input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameHeaders> { + let (i2, padlength) = cond(flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)(input)?; + let (i3, priority) = cond( + flags & HTTP2_FLAG_HEADER_PRIORITY != 0, + http2_parse_headers_priority, + )(i2)?; + let (i3, blocks) = http2_parse_headers_blocks(i3, dyn_headers)?; + return Ok(( + i3, + HTTP2FrameHeaders { + padlength, + priority, + blocks, + }, + )); +} + +#[derive(Clone, Debug)] +pub struct HTTP2FramePushPromise { + pub padlength: Option<u8>, + pub reserved: u8, + pub stream_id: u32, + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +pub fn http2_parse_frame_push_promise<'a>( + input: &'a [u8], flags: u8, dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FramePushPromise> { + let (i2, padlength) = cond(flags & HTTP2_FLAG_HEADER_PADDED != 0, be_u8)(input)?; + let (i3, stream_id) = bits(tuple((take_bits(1u8), take_bits(31u32))))(i2)?; + let (i3, blocks) = http2_parse_headers_blocks(i3, dyn_headers)?; + return Ok(( + i3, + HTTP2FramePushPromise { + padlength, + reserved: stream_id.0, + stream_id: stream_id.1, + blocks, + }, + )); +} + +#[derive(Clone, Debug)] +pub struct HTTP2FrameContinuation { + pub blocks: Vec<HTTP2FrameHeaderBlock>, +} + +pub fn http2_parse_frame_continuation<'a>( + input: &'a [u8], dyn_headers: &mut HTTP2DynTable, +) -> IResult<&'a [u8], HTTP2FrameContinuation> { + let (i3, blocks) = http2_parse_headers_blocks(input, dyn_headers)?; + return Ok((i3, HTTP2FrameContinuation { blocks })); +} + +#[repr(u16)] +#[derive(Clone, Copy, PartialEq, Eq, FromPrimitive, Debug)] +pub enum HTTP2SettingsId { + HeaderTableSize = 1, + EnablePush = 2, + MaxConcurrentStreams = 3, + InitialWindowSize = 4, + MaxFrameSize = 5, + MaxHeaderListSize = 6, +} + +impl fmt::Display for HTTP2SettingsId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for HTTP2SettingsId { + type Err = String; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "SETTINGS_HEADER_TABLE_SIZE" => Ok(HTTP2SettingsId::HeaderTableSize), + "SETTINGS_ENABLE_PUSH" => Ok(HTTP2SettingsId::EnablePush), + "SETTINGS_MAX_CONCURRENT_STREAMS" => Ok(HTTP2SettingsId::MaxConcurrentStreams), + "SETTINGS_INITIAL_WINDOW_SIZE" => Ok(HTTP2SettingsId::InitialWindowSize), + "SETTINGS_MAX_FRAME_SIZE" => Ok(HTTP2SettingsId::MaxFrameSize), + "SETTINGS_MAX_HEADER_LIST_SIZE" => Ok(HTTP2SettingsId::MaxHeaderListSize), + _ => Err(format!("'{}' is not a valid value for HTTP2SettingsId", s)), + } + } +} + +pub struct DetectHTTP2settingsSigCtx { + pub id: HTTP2SettingsId, //identifier + pub value: Option<DetectUintData<u32>>, //optional value +} + +pub fn http2_parse_settingsctx(i: &str) -> IResult<&str, DetectHTTP2settingsSigCtx> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, id) = map_opt(alt((complete(is_not(" <>=")), rest)), |s: &str| { + HTTP2SettingsId::from_str(s).ok() + })(i)?; + let (i, value) = opt(complete(detect_parse_uint))(i)?; + Ok((i, DetectHTTP2settingsSigCtx { id, value })) +} + +#[derive(Clone, Copy, Debug)] +pub struct HTTP2FrameSettings { + pub id: HTTP2SettingsId, + pub value: u32, +} + +fn http2_parse_frame_setting(i: &[u8]) -> IResult<&[u8], HTTP2FrameSettings> { + let (i, id) = map_opt(be_u16, num::FromPrimitive::from_u16)(i)?; + let (i, value) = be_u32(i)?; + Ok((i, HTTP2FrameSettings { id, value })) +} + +pub fn http2_parse_frame_settings(i: &[u8]) -> IResult<&[u8], Vec<HTTP2FrameSettings>> { + many0(complete(http2_parse_frame_setting))(i) +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::detect::uint::DetectUintMode; + + #[test] + fn test_http2_parse_header() { + let buf0: &[u8] = &[0x82]; + let mut dynh = HTTP2DynTable::new(); + let r0 = http2_parse_headers_block(buf0, &mut dynh); + match r0 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":method".as_bytes().to_vec()); + assert_eq!(hd.value, "GET".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf1: &[u8] = &[0x53, 0x03, 0x2A, 0x2F, 0x2A]; + let r1 = http2_parse_headers_block(buf1, &mut dynh); + match r1 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, "accept".as_bytes().to_vec()); + assert_eq!(hd.value, "*/*".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 1); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf: &[u8] = &[ + 0x41, 0x8a, 0xa0, 0xe4, 0x1d, 0x13, 0x9d, 0x09, 0xb8, 0xc8, 0x00, 0x0f, + ]; + let result = http2_parse_headers_block(buf, &mut dynh); + match result { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf3: &[u8] = &[0xbe]; + let r3 = http2_parse_headers_block(buf3, &mut dynh); + match r3 { + Ok((remainder, hd)) => { + // same as before + assert_eq!(hd.name, ":authority".as_bytes().to_vec()); + assert_eq!(hd.value, "localhost:3000".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf4: &[u8] = &[0x80]; + let r4 = http2_parse_headers_block(buf4, &mut dynh); + match r4 { + Ok((remainder, hd)) => { + assert_eq!(hd.error, HTTP2HeaderDecodeStatus::HTTP2HeaderDecodeIndex0); + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf2: &[u8] = &[ + 0x04, 0x94, 0x62, 0x43, 0x91, 0x8a, 0x47, 0x55, 0xa3, 0xa1, 0x89, 0xd3, 0x4d, 0x0c, + 0x1a, 0xa9, 0x0b, 0xe5, 0x79, 0xd3, 0x4d, 0x1f, + ]; + let r2 = http2_parse_headers_block(buf2, &mut dynh); + match r2 { + Ok((remainder, hd)) => { + // Check the first message. + assert_eq!(hd.name, ":path".as_bytes().to_vec()); + assert_eq!(hd.value, "/doc/manual/html/index.html".as_bytes().to_vec()); + // And we should have no bytes left. + assert_eq!(remainder.len(), 0); + assert_eq!(dynh.table.len(), 2); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + /// Simple test of some valid data. + #[test] + fn test_http2_parse_settingsctx() { + let s = "SETTINGS_ENABLE_PUSH"; + let r = http2_parse_settingsctx(s); + match r { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::EnablePush); + match ctx.value { + Some(_) => { + panic!("Unexpected value"); + } + None => {} + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + //spaces in the end + let s1 = "SETTINGS_ENABLE_PUSH "; + let r1 = http2_parse_settingsctx(s1); + match r1 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::EnablePush); + if ctx.value.is_some() { + panic!("Unexpected value"); + } + assert_eq!(rem.len(), 1); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s2 = "SETTINGS_MAX_CONCURRENT_STREAMS 42"; + let r2 = http2_parse_settingsctx(s2); + match r2 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 42); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s3 = "SETTINGS_MAX_CONCURRENT_STREAMS 42-68"; + let r3 = http2_parse_settingsctx(s3); + match r3 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 42); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeRange); + assert_eq!(ctxval.arg2, 68); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s4 = "SETTINGS_MAX_CONCURRENT_STREAMS<54"; + let r4 = http2_parse_settingsctx(s4); + match r4 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 54); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeLt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + + let s5 = "SETTINGS_MAX_CONCURRENT_STREAMS > 76"; + let r5 = http2_parse_settingsctx(s5); + match r5 { + Ok((rem, ctx)) => { + assert_eq!(ctx.id, HTTP2SettingsId::MaxConcurrentStreams); + match ctx.value { + Some(ctxval) => { + assert_eq!(ctxval.arg1, 76); + assert_eq!(ctxval.mode, DetectUintMode::DetectUintModeGt); + } + None => { + panic!("No value"); + } + } + assert_eq!(rem.len(), 0); + } + Err(e) => { + panic!("Result should not be an error {:?}.", e); + } + } + } + + #[test] + fn test_http2_parse_headers_block_string() { + let buf: &[u8] = &[0x01, 0xFF]; + let r = http2_parse_headers_block_string(buf); + match r { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + _ => { + panic!("Result should have been ok"); + } + } + let buf2: &[u8] = &[0x83, 0xFF, 0xFF, 0xEA]; + let r2 = http2_parse_headers_block_string(buf2); + match r2 { + Ok((remainder, _)) => { + assert_eq!(remainder.len(), 0); + } + _ => { + panic!("Result should have been ok"); + } + } + } + + #[test] + fn test_http2_parse_frame_header() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x06, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x64, + ]; + let result = http2_parse_frame_header(buf); + match result { + Ok((remainder, frame)) => { + // Check the first message. + assert_eq!(frame.length, 6); + assert_eq!(frame.ftype, HTTP2FrameType::Settings as u8); + assert_eq!(frame.flags, 0); + assert_eq!(frame.reserved, 0); + assert_eq!(frame.stream_id, 0); + + // And we should have 6 bytes left. + assert_eq!(remainder.len(), 6); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } +} diff --git a/rust/src/http2/range.rs b/rust/src/http2/range.rs new file mode 100644 index 0000000..9c96899 --- /dev/null +++ b/rust/src/http2/range.rs @@ -0,0 +1,259 @@ +/* Copyright (C) 2021 Open Information Security Foundation +* +* You can copy, redistribute or modify this Program under the terms of +* the GNU General Public License version 2 as published by the Free +* Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* version 2 along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. +*/ + +use super::detect; +use crate::core::{ + Direction, Flow, HttpRangeContainerBlock, StreamingBufferConfig, SuricataFileContext, SC, +}; +use crate::http2::http2::HTTP2Transaction; +use crate::http2::http2::SURICATA_HTTP2_FILE_CONFIG; + +use nom7::branch::alt; +use nom7::bytes::streaming::{take_till, take_while}; +use nom7::character::complete::{char, digit1}; +use nom7::combinator::{map_res, value}; +use nom7::error::{make_error, ErrorKind}; +use nom7::{Err, IResult}; +use std::os::raw::c_uchar; +use std::str::FromStr; + +#[derive(Debug)] +#[repr(C)] +pub struct HTTPContentRange { + pub start: i64, + pub end: i64, + pub size: i64, +} + +pub fn http2_parse_content_range_star(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, _) = char('*')(input)?; + let (i2, _) = char('/')(i2)?; + let (i2, size) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; + return Ok(( + i2, + HTTPContentRange { + start: -1, + end: -1, + size, + }, + )); +} + +pub fn http2_parse_content_range_def(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, start) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(input)?; + let (i2, _) = char('-')(i2)?; + let (i2, end) = map_res(map_res(digit1, std::str::from_utf8), i64::from_str)(i2)?; + let (i2, _) = char('/')(i2)?; + let (i2, size) = alt(( + value(-1, char('*')), + map_res(map_res(digit1, std::str::from_utf8), i64::from_str), + ))(i2)?; + return Ok((i2, HTTPContentRange { start, end, size })); +} + +fn http2_parse_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (i2, _) = take_while(|c| c == b' ')(input)?; + let (i2, _) = take_till(|c| c == b' ')(i2)?; + let (i2, _) = take_while(|c| c == b' ')(i2)?; + return alt(( + http2_parse_content_range_star, + http2_parse_content_range_def, + ))(i2); +} + +pub fn http2_parse_check_content_range(input: &[u8]) -> IResult<&[u8], HTTPContentRange> { + let (rem, v) = http2_parse_content_range(input)?; + if v.start > v.end || (v.end > 0 && v.size > 0 && v.end > v.size - 1) { + return Err(Err::Error(make_error(rem, ErrorKind::Verify))); + } + return Ok((rem, v)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_http_parse_content_range( + cr: &mut HTTPContentRange, buffer: *const u8, buffer_len: u32, +) -> std::os::raw::c_int { + let slice = build_slice!(buffer, buffer_len as usize); + match http2_parse_content_range(slice) { + Ok((_, c)) => { + *cr = c; + return 0; + } + _ => { + return -1; + } + } +} + +fn http2_range_key_get(tx: &mut HTTP2Transaction) -> Result<(Vec<u8>, usize), ()> { + let hostv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":authority")?; + let mut hostv = &hostv[..]; + if let Some(p) = hostv.iter().position(|&x| x == b':') { + hostv = &hostv[..p]; + } + let uriv = detect::http2_frames_get_header_value_vec(tx, Direction::ToServer, ":path")?; + let mut uriv = &uriv[..]; + if let Some(p) = uriv.iter().position(|&x| x == b'?') { + uriv = &uriv[..p]; + } + if let Some(p) = uriv.iter().rposition(|&x| x == b'/') { + uriv = &uriv[p..]; + } + let mut r = Vec::with_capacity(hostv.len() + uriv.len()); + r.extend_from_slice(hostv); + r.extend_from_slice(uriv); + return Ok((r, hostv.len())); +} + +pub fn http2_range_open( + tx: &mut HTTP2Transaction, v: &HTTPContentRange, flow: *const Flow, + cfg: &'static SuricataFileContext, dir: Direction, data: &[u8], +) { + if v.end <= 0 || v.size <= 0 { + // skipped for incomplete range information + return; + } + if v.end == v.size - 1 && v.start == 0 { + // whole file in one range + return; + } + let flags = if dir == Direction::ToServer { tx.ft_ts.file_flags } else { tx.ft_tc.file_flags }; + if let Ok((key, index)) = http2_range_key_get(tx) { + let name = &key[index..]; + tx.file_range = unsafe { + HttpRangeContainerOpenFile( + key.as_ptr(), + key.len() as u32, + flow, + v, + cfg.files_sbcfg, + name.as_ptr(), + name.len() as u16, + flags, + data.as_ptr(), + data.len() as u32, + ) + }; + } +} + +pub fn http2_range_append(cfg: &'static SuricataFileContext, fr: *mut HttpRangeContainerBlock, data: &[u8]) { + unsafe { + HttpRangeAppendData(cfg.files_sbcfg, fr, data.as_ptr(), data.len() as u32); + } +} + +pub fn http2_range_close( + tx: &mut HTTP2Transaction, dir: Direction, data: &[u8], +) { + let added = if let Some(c) = unsafe { SC } { + if let Some(sfcm) = unsafe { SURICATA_HTTP2_FILE_CONFIG } { + let (files, flags) = if dir == Direction::ToServer { + (&mut tx.ft_ts.file, tx.ft_ts.file_flags) + } else { + (&mut tx.ft_tc.file, tx.ft_tc.file_flags) + }; + let added = (c.HTPFileCloseHandleRange)( + sfcm.files_sbcfg, + files, + flags, + tx.file_range, + data.as_ptr(), + data.len() as u32, + ); + (c.HttpRangeFreeBlock)(tx.file_range); + added + } else { + false + } + } else { + false + }; + tx.file_range = std::ptr::null_mut(); + if added { + tx.tx_data.incr_files_opened(); + } +} + +// Defined in app-layer-htp-range.h +extern "C" { + pub fn HttpRangeContainerOpenFile( + key: *const c_uchar, keylen: u32, f: *const Flow, cr: &HTTPContentRange, + sbcfg: *const StreamingBufferConfig, name: *const c_uchar, name_len: u16, flags: u16, + data: *const c_uchar, data_len: u32, + ) -> *mut HttpRangeContainerBlock; + pub fn HttpRangeAppendData( + cfg: *const StreamingBufferConfig, c: *mut HttpRangeContainerBlock, data: *const c_uchar, data_len: u32, + ) -> std::os::raw::c_int; +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_http2_parse_content_range() { + let buf0: &[u8] = " bytes */100".as_bytes(); + let r0 = http2_parse_content_range(buf0); + match r0 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, -1); + assert_eq!(rg.end, -1); + assert_eq!(rg.size, 100); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + + let buf1: &[u8] = " bytes 10-20/200".as_bytes(); + let r1 = http2_parse_content_range(buf1); + match r1 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, 10); + assert_eq!(rg.end, 20); + assert_eq!(rg.size, 200); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + + let buf2: &[u8] = " bytes 30-68/*".as_bytes(); + let r2 = http2_parse_content_range(buf2); + match r2 { + Ok((rem, rg)) => { + // Check the first message. + assert_eq!(rg.start, 30); + assert_eq!(rg.end, 68); + assert_eq!(rg.size, -1); + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + } +} diff --git a/rust/src/ike/detect.rs b/rust/src/ike/detect.rs new file mode 100644 index 0000000..64d27a4 --- /dev/null +++ b/rust/src/ike/detect.rs @@ -0,0 +1,237 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +use super::ipsec_parser::IkeV2Transform; +use crate::ike::ike::*; +use std::ffi::CStr; +use std::ptr; + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_exch_type(tx: &mut IKETransaction, exch_type: *mut u8) -> u8 { + debug_validate_bug_on!(exch_type.is_null()); + + if tx.ike_version == 1 { + if let Some(r) = tx.hdr.ikev1_header.exchange_type { + unsafe { + *exch_type = r; + } + return 1; + } + } else if tx.ike_version == 2 { + unsafe { + *exch_type = tx.hdr.ikev2_header.exch_type.0; + } + return 1; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_spi_initiator( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer.is_null() || buffer_len.is_null()); + + unsafe { + *buffer = tx.hdr.spi_initiator.as_ptr(); + *buffer_len = tx.hdr.spi_initiator.len() as u32; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_spi_responder( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer.is_null() || buffer_len.is_null()); + + unsafe { + *buffer = tx.hdr.spi_responder.as_ptr(); + *buffer_len = tx.hdr.spi_responder.len() as u32; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_nonce( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer.is_null() || buffer_len.is_null()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.nonce.is_empty() { + let p = &tx.hdr.ikev1_header.nonce; + unsafe { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + } + return 1; + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_key_exchange( + tx: &mut IKETransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + debug_validate_bug_on!(buffer.is_null() || buffer_len.is_null()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.key_exchange.is_empty() { + let p = &tx.hdr.ikev1_header.key_exchange; + unsafe { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + } + return 1; + } + + unsafe { + *buffer = ptr::null(); + *buffer_len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_tx_get_vendor( + tx: &IKETransaction, i: u32, buf: *mut *const u8, len: *mut u32, +) -> u8 { + if tx.ike_version == 1 && i < tx.hdr.ikev1_header.vendor_ids.len() as u32 { + unsafe { + *len = tx.hdr.ikev1_header.vendor_ids[i as usize].len() as u32; + *buf = tx.hdr.ikev1_header.vendor_ids[i as usize].as_ptr(); + } + return 1; + } + + unsafe { + *buf = ptr::null(); + *len = 0; + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_get_sa_attribute( + tx: &mut IKETransaction, sa_type: *const std::os::raw::c_char, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value.is_null()); + let mut ret_val = 0; + let mut ret_code = 0; + let sa_type_s: Result<_, _>; + + unsafe { sa_type_s = CStr::from_ptr(sa_type).to_str() } + SCLogInfo!("{:#?}", sa_type_s); + + if let Ok(sa) = sa_type_s { + if tx.ike_version == 1 { + if !tx.hdr.ikev1_transforms.is_empty() { + // there should be only one chosen server_transform, check event + if let Some(server_transform) = tx.hdr.ikev1_transforms.first() { + for attr in server_transform { + if attr.attribute_type.to_string() == sa { + if let Some(numeric_value) = attr.numeric_value { + ret_val = numeric_value; + ret_code = 1; + break; + } + } + } + } + } + } else if tx.ike_version == 2 { + for attr in tx.hdr.ikev2_transforms.iter() { + match attr { + IkeV2Transform::Encryption(e) => { + if sa == "alg_enc" { + ret_val = e.0 as u32; + ret_code = 1; + break; + } + } + IkeV2Transform::Auth(e) => { + if sa == "alg_auth" { + ret_val = e.0 as u32; + ret_code = 1; + break; + } + } + IkeV2Transform::PRF(ref e) => { + if sa == "alg_prf" { + ret_val = e.0 as u32; + ret_code = 1; + break; + } + } + IkeV2Transform::DH(ref e) => { + if sa == "alg_dh" { + ret_val = e.0 as u32; + ret_code = 1; + break; + } + } + _ => (), + } + } + } + } + + unsafe { + *value = ret_val; + } + return ret_code; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_key_exchange_payload_length( + tx: &mut IKETransaction, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value.is_null()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.key_exchange.is_empty() { + *value = tx.hdr.ikev1_header.key_exchange.len() as u32; + return 1; + } + + *value = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_nonce_payload_length( + tx: &mut IKETransaction, value: *mut u32, +) -> u8 { + debug_validate_bug_on!(value.is_null()); + + if tx.ike_version == 1 && !tx.hdr.ikev1_header.nonce.is_empty() { + *value = tx.hdr.ikev1_header.nonce.len() as u32; + return 1; + } + + *value = 0; + return 0; +} diff --git a/rust/src/ike/ike.rs b/rust/src/ike/ike.rs new file mode 100644 index 0000000..2a5448a --- /dev/null +++ b/rust/src/ike/ike.rs @@ -0,0 +1,449 @@ +/* Copyright (C) 2020-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +extern crate ipsec_parser; +use self::ipsec_parser::*; + +use crate::applayer; +use crate::applayer::*; +use crate::core::{self, *}; +use crate::ike::ikev1::{handle_ikev1, IkeV1Header, Ikev1Container}; +use crate::ike::ikev2::{handle_ikev2, Ikev2Container}; +use crate::ike::parser::*; +use nom7::Err; +use std; +use std::collections::HashSet; +use std::ffi::CString; + +#[derive(AppLayerEvent)] +pub enum IkeEvent { + MalformedData, + NoEncryption, + WeakCryptoEnc, + WeakCryptoPrf, + WeakCryptoDh, + WeakCryptoAuth, + WeakCryptoNoDh, + WeakCryptoNoAuth, + InvalidProposal, + UnknownProposal, + PayloadExtraData, + MultipleServerProposal, +} + +pub struct IkeHeaderWrapper { + pub spi_initiator: String, + pub spi_responder: String, + pub maj_ver: u8, + pub min_ver: u8, + pub msg_id: u32, + pub flags: u8, + pub ikev1_transforms: Vec<Vec<SaAttribute>>, + pub ikev2_transforms: Vec<IkeV2Transform>, + pub ikev1_header: IkeV1Header, + pub ikev2_header: IkeV2Header, +} + +impl Default for IkeHeaderWrapper { + fn default() -> Self { + Self::new() + } +} + +impl IkeHeaderWrapper { + pub fn new() -> Self { + Self { + spi_initiator: String::new(), + spi_responder: String::new(), + maj_ver: 0, + min_ver: 0, + msg_id: 0, + flags: 0, + ikev1_transforms: Vec::new(), + ikev2_transforms: Vec::new(), + ikev1_header: IkeV1Header::default(), + ikev2_header: IkeV2Header { + init_spi: 0, + resp_spi: 0, + next_payload: IkePayloadType::NoNextPayload, + maj_ver: 0, + min_ver: 0, + exch_type: IkeExchangeType(0), + flags: 0, + msg_id: 0, + length: 0, + }, + } + } +} + +#[derive(Default)] +pub struct IkePayloadWrapper { + pub ikev1_payload_types: Option<HashSet<u8>>, + pub ikev2_payload_types: Vec<IkePayloadType>, +} + +#[derive(Default)] +pub struct IKETransaction { + tx_id: u64, + + pub ike_version: u8, + pub direction: Direction, + pub hdr: IkeHeaderWrapper, + pub payload_types: IkePayloadWrapper, + pub notify_types: Vec<NotifyType>, + + /// errors seen during exchange + pub errors: u32, + + logged: LoggerFlags, + pub tx_data: applayer::AppLayerTxData, +} + +impl Transaction for IKETransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +impl IKETransaction { + pub fn new(direction: Direction) -> Self { + Self { + direction, + tx_data: applayer::AppLayerTxData::for_direction(direction), + ..Default::default() + } + } + + /// Set an event. + pub fn set_event(&mut self, event: IkeEvent) { + self.tx_data.set_event(event as u8); + } +} + +#[derive(Default)] +pub struct IKEState { + state_data: AppLayerStateData, + tx_id: u64, + pub transactions: Vec<IKETransaction>, + + pub ikev1_container: Ikev1Container, + pub ikev2_container: Ikev2Container, +} + +impl State<IKETransaction> for IKEState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&IKETransaction> { + self.transactions.get(index) + } +} + +impl IKEState { + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let tx = self + .transactions + .iter() + .position(|tx| tx.tx_id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut IKETransaction> { + self.transactions.iter_mut().find(|tx| tx.tx_id == tx_id + 1) + } + + pub fn new_tx(&mut self, direction: Direction) -> IKETransaction { + let mut tx = IKETransaction::new(direction); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: IkeEvent) { + if let Some(tx) = self.transactions.last_mut() { + tx.set_event(event); + } else { + SCLogDebug!( + "IKE: trying to set event {} on non-existing transaction", + event as u32 + ); + } + } + + fn handle_input(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + let mut current = input; + match parse_isakmp_header(current) { + Ok((rem, isakmp_header)) => { + current = rem; + + if isakmp_header.maj_ver != 1 && isakmp_header.maj_ver != 2 { + SCLogDebug!("Unsupported ISAKMP major_version"); + return AppLayerResult::err(); + } + + if isakmp_header.maj_ver == 1 { + handle_ikev1(self, current, isakmp_header, direction); + } else if isakmp_header.maj_ver == 2 { + handle_ikev2(self, current, isakmp_header, direction); + } else { + return AppLayerResult::err(); + } + return AppLayerResult::ok(); // todo either remove outer loop or check header length-field if we have completely read everything + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing IKE"); + return AppLayerResult::err(); + } + Err(_) => { + SCLogDebug!("Error while parsing IKE packet"); + return AppLayerResult::err(); + } + } + } +} + +/// Probe to see if this input looks like a request or response. +fn probe(input: &[u8], direction: Direction, rdir: *mut u8) -> bool { + match parse_isakmp_header(input) { + Ok((_, isakmp_header)) => { + if isakmp_header.maj_ver == 1 { + if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { + unsafe { + *rdir = Direction::ToServer.into(); + } + } + return true; + } else if isakmp_header.maj_ver == 2 { + if isakmp_header.min_ver != 0 { + SCLogDebug!( + "ipsec_probe: could be ipsec, but with unsupported/invalid version {}.{}", + isakmp_header.maj_ver, + isakmp_header.min_ver + ); + return false; + } + if isakmp_header.exch_type < 34 || isakmp_header.exch_type > 37 { + SCLogDebug!("ipsec_probe: could be ipsec, but with unsupported/invalid exchange type {}", + isakmp_header.exch_type); + return false; + } + if isakmp_header.length as usize != input.len() { + SCLogDebug!("ipsec_probe: could be ipsec, but length does not match"); + return false; + } + + if isakmp_header.resp_spi == 0 && direction != Direction::ToServer { + unsafe { + *rdir = Direction::ToServer.into(); + } + } + return true; + } + + return false; + } + Err(_) => return false, + } +} + +// C exports. + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_ike_probing_parser( + _flow: *const Flow, direction: u8, input: *const u8, input_len: u32, rdir: *mut u8, +) -> AppProto { + if input_len < 28 { + // at least the ISAKMP_HEADER must be there, not ALPROTO_UNKNOWN because over UDP + return ALPROTO_FAILED; + } + + if !input.is_null() { + let slice = build_slice!(input, input_len as usize); + if probe(slice, direction.into(), rdir) { + return ALPROTO_IKE; + } + } + return ALPROTO_FAILED; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = IKEState::default(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(Box::from_raw(state as *mut IKEState)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, IKEState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_parse_request( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, IKEState); + return state.handle_input(stream_slice.as_slice(), Direction::ToServer); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_parse_response( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, IKEState); + return state.handle_input(stream_slice.as_slice(), Direction::ToClient); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, IKEState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, IKEState); + return state.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_ike_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { + // This parser uses 1 to signal transaction completion status. + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ike_tx_get_alstate_progress( + _tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_tx_get_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, +) -> u32 { + let tx = cast_pointer!(tx, IKETransaction); + return tx.logged.get(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_tx_set_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, logged: u32, +) { + let tx = cast_pointer!(tx, IKETransaction); + tx.logged.set(logged); +} + +static mut ALPROTO_IKE: AppProto = ALPROTO_UNKNOWN; + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"ike\0"; +const PARSER_ALIAS: &[u8] = b"ikev2\0"; + +export_tx_data_get!(rs_ike_get_tx_data, IKETransaction); +export_state_data_get!(rs_ike_get_state_data, IKEState); + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_register_parser() { + let default_port = CString::new("500").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: core::IPPROTO_UDP, + probe_ts: Some(rs_ike_probing_parser), + probe_tc: Some(rs_ike_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_ike_state_new, + state_free: rs_ike_state_free, + tx_free: rs_ike_state_tx_free, + parse_ts: rs_ike_parse_request, + parse_tc: rs_ike_parse_response, + get_tx_count: rs_ike_state_get_tx_count, + get_tx: rs_ike_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_ike_tx_get_alstate_progress, + get_eventinfo: Some(IkeEvent::get_event_info), + get_eventinfo_byid: Some(IkeEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<IKEState, IKETransaction>), + get_tx_data: rs_ike_get_tx_data, + get_state_data: rs_ike_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_IKE = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + + AppLayerRegisterParserAlias( + PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + PARSER_ALIAS.as_ptr() as *const std::os::raw::c_char, + ); + SCLogDebug!("Rust IKE parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for IKE."); + } +} diff --git a/rust/src/ike/ikev1.rs b/rust/src/ike/ikev1.rs new file mode 100644 index 0000000..1e79c29 --- /dev/null +++ b/rust/src/ike/ikev1.rs @@ -0,0 +1,169 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +use crate::applayer::*; +use crate::common::to_hex; +use crate::core::Direction; +use crate::ike::ike::{IKEState, IkeEvent}; +use crate::ike::parser::*; +use nom7::Err; +use std; +use std::collections::HashSet; + +#[derive(Default)] +pub struct IkeV1Header { + pub exchange_type: Option<u8>, + pub encrypted_payloads: bool, + + pub key_exchange: Vec<u8>, + pub nonce: Vec<u8>, + pub vendor_ids: Vec<String>, +} + +#[derive(Default)] +pub struct Ikev1ParticipantData { + pub key_exchange: String, + pub nonce: String, + pub nb_transforms: u64, + pub transform: Vec<SaAttribute>, +} + +impl Ikev1ParticipantData { + pub fn reset(&mut self) { + self.key_exchange.clear(); + self.nonce.clear(); + self.nb_transforms = 0; + self.transform.clear(); + } + + pub fn update( + &mut self, key_exchange: &str, nonce: &str, transforms: &Vec<Vec<SaAttribute>>, + ) { + self.key_exchange = key_exchange.to_string(); + self.nonce = nonce.to_string(); + if self.nb_transforms == 0 && !transforms.is_empty() { + self.transform.extend(transforms[0].iter().cloned()); + } + self.nb_transforms += transforms.len() as u64; + } +} + +#[derive(Default)] +pub struct Ikev1Container { + pub domain_of_interpretation: Option<u32>, + pub client: Ikev1ParticipantData, + pub server: Ikev1ParticipantData, +} + +pub fn handle_ikev1( + state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction, +) -> AppLayerResult { + let mut tx = state.new_tx(direction); + + tx.ike_version = 1; + tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi); + tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi); + tx.hdr.maj_ver = isakmp_header.maj_ver; + tx.hdr.min_ver = isakmp_header.min_ver; + tx.hdr.ikev1_header.exchange_type = Some(isakmp_header.exch_type); + tx.hdr.msg_id = isakmp_header.msg_id; + tx.hdr.flags = isakmp_header.flags; + + let mut cur_payload_type = isakmp_header.next_payload; + let mut payload_types: HashSet<u8> = HashSet::new(); + payload_types.insert(cur_payload_type); + + if isakmp_header.flags & 0x01 != 0x01 { + match parse_ikev1_payload_list(current) { + Ok((rem, payload_list)) => { + for isakmp_payload in payload_list { + if parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + &mut state.ikev1_container.domain_of_interpretation, + &mut tx.hdr.ikev1_header.key_exchange, + &mut tx.hdr.ikev1_header.nonce, + &mut tx.hdr.ikev1_transforms, + &mut tx.hdr.ikev1_header.vendor_ids, + &mut payload_types, + ).is_err() { + SCLogDebug!("Error while parsing IKEV1 payloads"); + return AppLayerResult::err(); + } + + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + + if payload_types.contains(&(IsakmpPayloadType::SecurityAssociation as u8)) { + // clear transforms on a new SA in case there is happening a new key exchange + // on the same flow, elsewise properties would be added to the old/other SA + if direction == Direction::ToServer { + state.ikev1_container.client.reset(); + } else { + state.ikev1_container.server.reset(); + } + } + + // add transaction values to state values + if direction == Direction::ToServer { + state.ikev1_container.client.update( + &to_hex(tx.hdr.ikev1_header.key_exchange.as_ref()), + &to_hex(tx.hdr.ikev1_header.nonce.as_ref()), + &tx.hdr.ikev1_transforms, + ); + } else { + if state.ikev1_container.server.nb_transforms <= 1 + && state.ikev1_container.server.nb_transforms + + tx.hdr.ikev1_transforms.len() as u64 + > 1 + { + SCLogDebug!("More than one chosen server proposal"); + state.set_event(IkeEvent::MultipleServerProposal); + } + + state.ikev1_container.server.update( + &to_hex(tx.hdr.ikev1_header.key_exchange.as_ref()), + &to_hex(tx.hdr.ikev1_header.nonce.as_ref()), + &tx.hdr.ikev1_transforms, + ); + } + + if !rem.is_empty() { + // more data left unread than should be + SCLogDebug!("Unread Payload Data"); + state.set_event(IkeEvent::PayloadExtraData); + } + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing IKEV1"); + return AppLayerResult::err(); + } + Err(_) => { + SCLogDebug!("Error while parsing payloads and adding to the state"); + return AppLayerResult::err(); + } + } + } + + tx.payload_types.ikev1_payload_types = Some(payload_types); + tx.hdr.ikev1_header.encrypted_payloads = isakmp_header.flags & 0x01 == 0x01; + state.transactions.push(tx); + return AppLayerResult::ok(); +} diff --git a/rust/src/ike/ikev2.rs b/rust/src/ike/ikev2.rs new file mode 100644 index 0000000..a1be25f --- /dev/null +++ b/rust/src/ike/ikev2.rs @@ -0,0 +1,316 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::applayer::*; +use crate::core::Direction; +use crate::ike::ipsec_parser::*; + +use super::ipsec_parser::IkeV2Transform; +use crate::ike::ike::{IKEState, IKETransaction, IkeEvent}; +use crate::ike::parser::IsakmpHeader; +use ipsec_parser::{IkeExchangeType, IkePayloadType, IkeV2Header}; + +#[derive(Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum IKEV2ConnectionState { + Init, + InitSASent, + InitKESent, + InitNonceSent, + RespSASent, + RespKESent, + + ParsingDone, + + Invalid, +} + +impl IKEV2ConnectionState { + pub fn advance(&self, payload: &IkeV2Payload) -> IKEV2ConnectionState { + use self::IKEV2ConnectionState::*; + match (self, &payload.content) { + (&Init, &IkeV2PayloadContent::SA(_)) => InitSASent, + (&InitSASent, &IkeV2PayloadContent::KE(_)) => InitKESent, + (&InitKESent, &IkeV2PayloadContent::Nonce(_)) => InitNonceSent, + (&InitNonceSent, &IkeV2PayloadContent::SA(_)) => RespSASent, + (&RespSASent, &IkeV2PayloadContent::KE(_)) => RespKESent, + (&RespKESent, &IkeV2PayloadContent::Nonce(_)) => ParsingDone, // should go to RespNonceSent, + (&ParsingDone, _) => self.clone(), + (_, &IkeV2PayloadContent::Notify(_)) => self.clone(), + (_, &IkeV2PayloadContent::Dummy) => self.clone(), + (_, _) => Invalid, + } + } +} + +pub struct Ikev2Container { + /// The connection state + pub connection_state: IKEV2ConnectionState, + + /// The transforms proposed by the initiator + pub client_transforms: Vec<Vec<IkeV2Transform>>, + + /// The encryption algorithm selected by the responder + pub alg_enc: IkeTransformEncType, + /// The authentication algorithm selected by the responder + pub alg_auth: IkeTransformAuthType, + /// The PRF algorithm selected by the responder + pub alg_prf: IkeTransformPRFType, + /// The Diffie-Hellman algorithm selected by the responder + pub alg_dh: IkeTransformDHType, + /// The extended sequence numbers parameter selected by the responder + pub alg_esn: IkeTransformESNType, + /// The Diffie-Hellman group from the server KE message, if present. + pub dh_group: IkeTransformDHType, +} + +impl Default for Ikev2Container { + fn default() -> Ikev2Container { + Ikev2Container { + connection_state: IKEV2ConnectionState::Init, + dh_group: IkeTransformDHType::None, + client_transforms: Vec::new(), + alg_enc: IkeTransformEncType::ENCR_NULL, + alg_auth: IkeTransformAuthType::NONE, + alg_prf: IkeTransformPRFType::PRF_NULL, + alg_dh: IkeTransformDHType::None, + alg_esn: IkeTransformESNType::NoESN, + } + } +} + +pub fn handle_ikev2( + state: &mut IKEState, current: &[u8], isakmp_header: IsakmpHeader, direction: Direction, +) -> AppLayerResult { + let hdr = IkeV2Header { + init_spi: isakmp_header.init_spi, + resp_spi: isakmp_header.resp_spi, + next_payload: IkePayloadType(isakmp_header.next_payload), + maj_ver: isakmp_header.maj_ver, + min_ver: isakmp_header.min_ver, + exch_type: IkeExchangeType(isakmp_header.exch_type), + flags: isakmp_header.flags, + msg_id: isakmp_header.msg_id, + length: isakmp_header.length, + }; + + let mut tx = state.new_tx(direction); + tx.ike_version = 2; + // use init_spi as transaction identifier + // tx.xid = hdr.init_spi; todo is this used somewhere? + tx.hdr.ikev2_header = hdr.clone(); + tx.hdr.spi_initiator = format!("{:016x}", isakmp_header.init_spi); + tx.hdr.spi_responder = format!("{:016x}", isakmp_header.resp_spi); + tx.hdr.maj_ver = isakmp_header.maj_ver; + tx.hdr.min_ver = isakmp_header.min_ver; + tx.hdr.msg_id = isakmp_header.msg_id; + tx.hdr.flags = isakmp_header.flags; + let mut payload_types = Vec::new(); + let mut errors = 0; + let mut notify_types = Vec::new(); + match parse_ikev2_payload_list(current, hdr.next_payload) { + Ok((_, Ok(ref p))) => { + for payload in p { + payload_types.push(payload.hdr.next_payload_type); + match payload.content { + IkeV2PayloadContent::Dummy => (), + IkeV2PayloadContent::SA(ref prop) => { + // if hdr.flags & IKEV2_FLAG_INITIATOR != 0 { + add_proposals(state, &mut tx, prop, direction); + // } + } + IkeV2PayloadContent::KE(ref kex) => { + SCLogDebug!("KEX {:?}", kex.dh_group); + if direction == Direction::ToClient { + state.ikev2_container.dh_group = kex.dh_group; + } + } + IkeV2PayloadContent::Nonce(ref _n) => { + SCLogDebug!("Nonce: {:?}", _n); + } + IkeV2PayloadContent::Notify(ref n) => { + SCLogDebug!("Notify: {:?}", n); + if n.notify_type.is_error() { + errors += 1; + } + notify_types.push(n.notify_type); + } + // XXX CertificateRequest + // XXX Certificate + // XXX Authentication + // XXX TSi + // XXX TSr + // XXX IDr + _ => { + SCLogDebug!("Unknown payload content {:?}", payload.content); + } + } + state.ikev2_container.connection_state = + state.ikev2_container.connection_state.advance(payload); + tx.payload_types + .ikev2_payload_types + .append(&mut payload_types); + tx.errors = errors; + tx.notify_types.append(&mut notify_types); + } + } + _e => { + SCLogDebug!("parse_ikev2_payload_with_type: {:?}", _e); + } + } + state.transactions.push(tx); + return AppLayerResult::ok(); +} + +fn add_proposals( + state: &mut IKEState, tx: &mut IKETransaction, prop: &Vec<IkeV2Proposal>, direction: Direction, +) { + for p in prop { + let transforms: Vec<IkeV2Transform> = p.transforms.iter().map(|x| x.into()).collect(); + // Rule 1: warn on weak or unknown transforms + for xform in &transforms { + match *xform { + IkeV2Transform::Encryption(ref enc) => { + match *enc { + IkeTransformEncType::ENCR_DES_IV64 + | IkeTransformEncType::ENCR_DES + | IkeTransformEncType::ENCR_3DES + | IkeTransformEncType::ENCR_RC5 + | IkeTransformEncType::ENCR_IDEA + | IkeTransformEncType::ENCR_CAST + | IkeTransformEncType::ENCR_BLOWFISH + | IkeTransformEncType::ENCR_3IDEA + | IkeTransformEncType::ENCR_DES_IV32 + | IkeTransformEncType::ENCR_NULL => { + SCLogDebug!("Weak Encryption: {:?}", enc); + // XXX send event only if direction == Direction::ToClient ? + tx.set_event(IkeEvent::WeakCryptoEnc); + } + _ => (), + } + } + IkeV2Transform::PRF(ref prf) => match *prf { + IkeTransformPRFType::PRF_NULL => { + SCLogDebug!("'Null' PRF transform proposed"); + tx.set_event(IkeEvent::InvalidProposal); + } + IkeTransformPRFType::PRF_HMAC_MD5 | IkeTransformPRFType::PRF_HMAC_SHA1 => { + SCLogDebug!("Weak PRF: {:?}", prf); + tx.set_event(IkeEvent::WeakCryptoPrf); + } + _ => (), + }, + IkeV2Transform::Auth(ref auth) => { + match *auth { + IkeTransformAuthType::NONE => { + // Note: this could be expected with an AEAD encryption alg. + // See rule 4 + } + IkeTransformAuthType::AUTH_HMAC_MD5_96 + | IkeTransformAuthType::AUTH_HMAC_SHA1_96 + | IkeTransformAuthType::AUTH_DES_MAC + | IkeTransformAuthType::AUTH_KPDK_MD5 + | IkeTransformAuthType::AUTH_AES_XCBC_96 + | IkeTransformAuthType::AUTH_HMAC_MD5_128 + | IkeTransformAuthType::AUTH_HMAC_SHA1_160 => { + SCLogDebug!("Weak auth: {:?}", auth); + tx.set_event(IkeEvent::WeakCryptoAuth); + } + _ => (), + } + } + IkeV2Transform::DH(ref dh) => match *dh { + IkeTransformDHType::None => { + SCLogDebug!("'None' DH transform proposed"); + tx.set_event(IkeEvent::InvalidProposal); + } + IkeTransformDHType::Modp768 + | IkeTransformDHType::Modp1024 + | IkeTransformDHType::Modp1024s160 + | IkeTransformDHType::Modp1536 => { + SCLogDebug!("Weak DH: {:?}", dh); + tx.set_event(IkeEvent::WeakCryptoDh); + } + _ => (), + }, + IkeV2Transform::Unknown(_tx_type, _tx_id) => { + SCLogDebug!("Unknown proposal: type={:?}, id={}", _tx_type, _tx_id); + tx.set_event(IkeEvent::UnknownProposal); + } + _ => (), + } + } + // Rule 2: check if no DH was proposed + if !transforms.iter().any(|x| match *x { + IkeV2Transform::DH(_) => true, + _ => false, + }) { + SCLogDebug!("No DH transform found"); + tx.set_event(IkeEvent::WeakCryptoNoDh); + } + // Rule 3: check if proposing AH ([RFC7296] section 3.3.1) + if p.protocol_id == ProtocolID::AH { + SCLogDebug!("Proposal uses protocol AH - no confidentiality"); + tx.set_event(IkeEvent::NoEncryption); + } + // Rule 4: lack of integrity is accepted only if using an AEAD proposal + // Look if no auth was proposed, including if proposal is Auth::None + if !transforms.iter().any(|x| match *x { + IkeV2Transform::Auth(IkeTransformAuthType::NONE) => false, + IkeV2Transform::Auth(_) => true, + _ => false, + }) && !transforms.iter().any(|x| match *x { + IkeV2Transform::Encryption(ref enc) => enc.is_aead(), + _ => false, + }) { + SCLogDebug!("No integrity transform found"); + tx.set_event(IkeEvent::WeakCryptoNoAuth); + } + // Finally + if direction == Direction::ToClient { + transforms.iter().for_each(|t| match *t { + IkeV2Transform::Encryption(ref e) => { + state.ikev2_container.alg_enc = *e; + tx.hdr.ikev2_transforms.push(IkeV2Transform::Encryption(*e)); + } + IkeV2Transform::Auth(ref a) => { + state.ikev2_container.alg_auth = *a; + tx.hdr.ikev2_transforms.push(IkeV2Transform::Auth(*a)); + } + IkeV2Transform::PRF(ref p) => { + state.ikev2_container.alg_prf = *p; + tx.hdr.ikev2_transforms.push(IkeV2Transform::PRF(*p)); + } + IkeV2Transform::DH(ref dh) => { + state.ikev2_container.alg_dh = *dh; + tx.hdr.ikev2_transforms.push(IkeV2Transform::DH(*dh)); + } + IkeV2Transform::ESN(ref e) => { + state.ikev2_container.alg_esn = *e; + tx.hdr.ikev2_transforms.push(IkeV2Transform::ESN(*e)); + } + _ => {} + }); + SCLogDebug!("Selected transforms: {:?}", transforms); + } else { + SCLogDebug!("Proposed transforms: {:?}", transforms); + state.ikev2_container.client_transforms.push(transforms); + } + } +} diff --git a/rust/src/ike/logger.rs b/rust/src/ike/logger.rs new file mode 100644 index 0000000..cc5705f --- /dev/null +++ b/rust/src/ike/logger.rs @@ -0,0 +1,234 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::ike::{IKEState, IKETransaction}; +use super::ipsec_parser::IKEV2_FLAG_INITIATOR; +use crate::core::Direction; +use crate::ike::parser::{ExchangeType, IsakmpPayloadType, SaAttribute}; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use num_traits::FromPrimitive; +use std; +use std::convert::TryFrom; + +const LOG_EXTENDED: u32 = 0x01; + +fn add_attributes(transform: &Vec<SaAttribute>, js: &mut JsonBuilder) -> Result<(), JsonError> { + for attribute in transform { + js.set_string( + attribute.attribute_type.to_string().as_str(), + attribute.attribute_value.to_string().as_str(), + )?; + + if let Some(numeric_value) = attribute.numeric_value { + js.set_uint( + format!("{}_raw", attribute.attribute_type).as_str(), + numeric_value as u64, + )?; + } else if let Some(hex_value) = &attribute.hex_value { + js.set_string( + format!("{}_raw", attribute.attribute_type).as_str(), + hex_value, + )?; + } + } + + return Ok(()); +} + +fn log_ike( + state: &IKEState, tx: &IKETransaction, flags: u32, jb: &mut JsonBuilder, +) -> Result<(), JsonError> { + jb.open_object("ike")?; + + jb.set_uint("version_major", tx.hdr.maj_ver as u64)?; + jb.set_uint("version_minor", tx.hdr.min_ver as u64)?; + jb.set_string("init_spi", &tx.hdr.spi_initiator)?; + jb.set_string("resp_spi", &tx.hdr.spi_responder)?; + jb.set_uint("message_id", tx.hdr.msg_id as u64)?; + + if tx.ike_version == 1 { + if let Some(exchange_type) = tx.hdr.ikev1_header.exchange_type { + jb.set_uint("exchange_type", exchange_type as u64)?; + if (flags & LOG_EXTENDED) == LOG_EXTENDED { + if let Some(etype) = ExchangeType::from_u8(exchange_type) { + jb.set_string("exchange_type_verbose", etype.to_string().as_str())?; + }; + } + } + } else if tx.ike_version == 2 { + jb.set_uint("exchange_type", tx.hdr.ikev2_header.exch_type.0 as u64)?; + } + + if tx.ike_version == 1 { + if state.ikev1_container.server.nb_transforms > 0 { + // log the first transform as the chosen one + add_attributes(&state.ikev1_container.server.transform, jb)?; + } + if tx.direction == Direction::ToClient && tx.hdr.ikev1_transforms.len() > 1 { + // in case we have multiple server transforms log them in a list + jb.open_array("server_proposals")?; + for server_transform in &tx.hdr.ikev1_transforms { + jb.start_object()?; + add_attributes(server_transform, jb)?; + jb.close()?; + } + jb.close()?; + } + } else if tx.ike_version == 2 { + if tx.hdr.flags & IKEV2_FLAG_INITIATOR != 0 { + jb.set_string("role", "initiator")?; + } else { + jb.set_string("role", "responder")?; + jb.set_string("alg_enc", &format!("{:?}", state.ikev2_container.alg_enc))?; + jb.set_string("alg_auth", &format!("{:?}", state.ikev2_container.alg_auth))?; + jb.set_string("alg_prf", &format!("{:?}", state.ikev2_container.alg_prf))?; + jb.set_string("alg_dh", &format!("{:?}", state.ikev2_container.alg_dh))?; + jb.set_string("alg_esn", &format!("{:?}", state.ikev2_container.alg_esn))?; + } + } + + // payloads in packet + jb.open_array("payload")?; + if tx.ike_version == 1 { + if let Some(payload_types) = &tx.payload_types.ikev1_payload_types { + for pt in payload_types { + append_payload_type_extended(jb, pt)?; + } + } + } else if tx.ike_version == 2 { + for payload in tx.payload_types.ikev2_payload_types.iter() { + jb.append_string(&format!("{:?}", payload))?; + } + } + jb.close()?; + + if tx.ike_version == 1 { + log_ikev1(state, tx, jb)?; + } else if tx.ike_version == 2 { + log_ikev2(tx, jb)?; + } + jb.close()?; + return Ok(()); +} + +fn log_ikev1(state: &IKEState, tx: &IKETransaction, jb: &mut JsonBuilder) -> Result<(), JsonError> { + jb.open_object("ikev1")?; + + if let Some(doi) = state.ikev1_container.domain_of_interpretation { + jb.set_uint("doi", doi as u64)?; + } + jb.set_bool("encrypted_payloads", tx.hdr.ikev1_header.encrypted_payloads)?; + + if !tx.hdr.ikev1_header.encrypted_payloads { + // enable logging of collected state if not-encrypted payloads + + // client data + jb.open_object("client")?; + if !state.ikev1_container.client.key_exchange.is_empty() { + jb.set_string( + "key_exchange_payload", + &state.ikev1_container.client.key_exchange, + )?; + if let Ok(client_key_length) = + u64::try_from(state.ikev1_container.client.key_exchange.len()) + { + jb.set_uint("key_exchange_payload_length", client_key_length / 2)?; + } + } + if !state.ikev1_container.client.nonce.is_empty() { + jb.set_string("nonce_payload", &state.ikev1_container.client.nonce)?; + if let Ok(client_nonce_length) = u64::try_from(state.ikev1_container.client.nonce.len()) + { + jb.set_uint("nonce_payload_length", client_nonce_length / 2)?; + } + } + + if tx.direction == Direction::ToServer && !tx.hdr.ikev1_transforms.is_empty() { + jb.open_array("proposals")?; + for client_transform in &tx.hdr.ikev1_transforms { + jb.start_object()?; + add_attributes(client_transform, jb)?; + jb.close()?; + } + jb.close()?; // proposals + } + jb.close()?; // client + + // server data + jb.open_object("server")?; + if !state.ikev1_container.server.key_exchange.is_empty() { + jb.set_string( + "key_exchange_payload", + &state.ikev1_container.server.key_exchange, + )?; + if let Ok(server_key_length) = + u64::try_from(state.ikev1_container.server.key_exchange.len()) + { + jb.set_uint("key_exchange_payload_length", server_key_length / 2)?; + } + } + if !state.ikev1_container.server.nonce.is_empty() { + jb.set_string("nonce_payload", &state.ikev1_container.server.nonce)?; + if let Ok(server_nonce_length) = u64::try_from(state.ikev1_container.server.nonce.len()) + { + jb.set_uint("nonce_payload_length", server_nonce_length / 2)?; + } + } + jb.close()?; // server + + if !tx.hdr.ikev1_header.vendor_ids.is_empty() { + jb.open_array("vendor_ids")?; + for vendor in &tx.hdr.ikev1_header.vendor_ids { + jb.append_string(vendor)?; + } + jb.close()?; // vendor_ids + } + } + jb.close()?; + + return Ok(()); +} + +fn append_payload_type_extended(js: &mut JsonBuilder, pt: &u8) -> Result<(), JsonError> { + if let Some(v) = IsakmpPayloadType::from_u8(*pt) { + js.append_string(&format!("{:?}", v))?; + } + Ok(()) +} + +fn log_ikev2(tx: &IKETransaction, jb: &mut JsonBuilder) -> Result<(), JsonError> { + jb.open_object("ikev2")?; + + jb.set_uint("errors", tx.errors as u64)?; + if !tx.notify_types.is_empty() { + jb.open_array("notify")?; + for notify in tx.notify_types.iter() { + jb.append_string(&format!("{:?}", notify))?; + } + jb.close()?; + } + jb.close()?; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ike_logger_log( + state: &mut IKEState, tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, IKETransaction); + log_ike(state, tx, flags, js).is_ok() +} diff --git a/rust/src/ike/mod.rs b/rust/src/ike/mod.rs new file mode 100644 index 0000000..366688e --- /dev/null +++ b/rust/src/ike/mod.rs @@ -0,0 +1,29 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! IKE parser, detection, logger and application layer module. + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +extern crate ipsec_parser; + +mod detect; +pub mod ike; +mod ikev1; +mod ikev2; +pub mod logger; +mod parser; diff --git a/rust/src/ike/parser.rs b/rust/src/ike/parser.rs new file mode 100644 index 0000000..dcc5745 --- /dev/null +++ b/rust/src/ike/parser.rs @@ -0,0 +1,712 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::common::to_hex; +use core::fmt; +use nom7::bytes::streaming::take; +use nom7::combinator::{complete, cond, map}; +use nom7::multi::many0; +use nom7::number::streaming::{be_u16, be_u32, be_u64, be_u8}; +use nom7::{Err, IResult}; +use std::collections::HashSet; + +// Generic ISAKMP "Container" structs +#[repr(u8)] +#[derive(Copy, Clone, FromPrimitive)] +pub enum ExchangeType { + None = 0, + Base = 1, + IdentityProtection = 2, + AuthenticationOnly = 3, + Aggressive = 4, + Informational = 5, + Transaction = 6, + QuickMode = 32, + NewGroupMode = 33, +} + +impl fmt::Display for ExchangeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ExchangeType::Base => write!(f, "Base"), + ExchangeType::IdentityProtection => write!(f, "Identity Protection"), + ExchangeType::AuthenticationOnly => write!(f, "Authentication Only"), + ExchangeType::Aggressive => write!(f, "Aggressive"), + ExchangeType::Informational => write!(f, "Informational"), + ExchangeType::Transaction => write!(f, "Transaction (Config Mode)"), + ExchangeType::QuickMode => write!(f, "Quick Mode"), + ExchangeType::NewGroupMode => write!(f, "New Group Mode"), + _ => write!(f, "Unknown Exchange Type"), + } + } +} + +pub struct IsakmpHeader { + pub init_spi: u64, + pub resp_spi: u64, + pub next_payload: u8, + pub maj_ver: u8, + pub min_ver: u8, + pub exch_type: u8, + pub flags: u8, + pub msg_id: u32, + pub length: u32, +} + +pub struct IsakmpPayloadHeader { + pub next_payload: u8, + pub reserved: u8, + pub payload_length: u16, +} + +pub struct IsakmpPayload<'a> { + pub payload_header: IsakmpPayloadHeader, + pub data: &'a [u8], +} + +// IKEV1 specific payloads + +// 1 -> Security Association +pub struct SecurityAssociationPayload<'a> { + pub domain_of_interpretation: u32, + pub situation: Option<&'a [u8]>, + pub data: Option<&'a [u8]>, +} + +// 2 -> Proposal +pub struct ProposalPayload<'a> { + pub proposal_number: u8, + pub proposal_type: u8, + pub spi_size: u8, + pub number_transforms: u8, + pub spi: &'a [u8], + pub data: &'a [u8], +} + +// 3 -> Transform +pub struct TransformPayload<'a> { + pub transform_number: u8, + pub transform_type: u8, + pub sa_attributes: &'a [u8], +} + +// 4 -> Key Exchange +pub struct KeyExchangePayload<'a> { + pub key_exchange_data: &'a [u8], +} + +// 5 -> Identification +// 6 -> Certificate +// 7 -> Certificate Request +// 8 -> Hash +// 9 -> Signature + +// 10 -> Nonce +pub struct NoncePayload<'a> { + pub nonce_data: &'a [u8], +} + +// 11 -> Notification +// 12 -> Delete + +// 13 -> Vendor ID +pub struct VendorPayload<'a> { + pub vendor_id: &'a [u8], +} + +// Attributes inside Transform +#[derive(Debug, Clone)] +pub enum AttributeType { + Unknown = 0, + EncryptionAlgorithm = 1, + HashAlgorithm = 2, + AuthenticationMethod = 3, + GroupDescription = 4, + GroupType = 5, + GroupPrime = 6, + GroupGeneratorOne = 7, + GroupGeneratorTwo = 8, + GroupCurveA = 9, + GroupCurveB = 10, + LifeType = 11, + LifeDuration = 12, + Prf = 13, + KeyLength = 14, + FieldSize = 15, + GroupOrder = 16, +} + +impl fmt::Display for AttributeType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + AttributeType::EncryptionAlgorithm => write!(f, "alg_enc"), + AttributeType::HashAlgorithm => write!(f, "alg_hash"), + AttributeType::AuthenticationMethod => write!(f, "alg_auth"), + AttributeType::GroupDescription => write!(f, "alg_dh"), + AttributeType::GroupType => write!(f, "sa_group_type"), + AttributeType::GroupPrime => write!(f, "sa_group_prime"), + AttributeType::GroupGeneratorOne => write!(f, "sa_group_generator_one"), + AttributeType::GroupGeneratorTwo => write!(f, "sa_group_generator_two"), + AttributeType::GroupCurveA => write!(f, "sa_group_curve_a"), + AttributeType::GroupCurveB => write!(f, "sa_group_curve_b"), + AttributeType::LifeType => write!(f, "sa_life_type"), + AttributeType::LifeDuration => write!(f, "sa_life_duration"), + AttributeType::Prf => write!(f, "alg_prf"), + AttributeType::KeyLength => write!(f, "sa_key_length"), + AttributeType::FieldSize => write!(f, "sa_field_size"), + AttributeType::GroupOrder => write!(f, "sa_group_order"), + _ => write!(f, "unknown"), + } + } +} + +#[derive(Debug, Clone)] +pub enum AttributeValue { + // https://www.iana.org/assignments/ipsec-registry/ipsec-registry.xhtml + Unknown, + // Encryption Algorithm + EncDesCbc, + EncIdeaCbc, + EncBlowfishCbc, + EncRc5R16B64Cbc, + EncTripleDesCbc, + EncCastCbc, + EncAesCbc, + EncCamelliaCbc, + // Hash Algorithm + HashMd5, + HashSha, + HashTiger, + HashSha2_256, + HashSha2_384, + HashSha2_512, + // Authentication Method + AuthPreSharedKey, + AuthDssSignatures, + AuthRsaSignatures, + AuthEncryptionWithRsa, + AuthRevisedEncryptionWithRsa, + AuthReserved, + AuthEcdsaSha256, + AuthEcdsaSha384, + AuthEcdsaSha512, + // Group Description + GroupDefault768BitModp, + GroupAlternate1024BitModpGroup, + GroupEc2nOnGp2p155, + GroupEc2nOnGp2p185, + GroupModp1536Bit, + GroupEc2nOverGf2p163, + GroupEc2nOverGf2p283, + GroupEc2nOverGf2p409, + GroupEc2nOverGf2p571, + GroupModp2048Bit, + GroupModp3072Bit, + GroupModp4096Bit, + GroupModp6144Bit, + GroupModp8192Bit, + GroupRandomEcp256, + GroupRandomEcp384, + GroupRandomEcp521, + GroupModp1024With160BitPrime, + GroupModp2048With224BitPrime, + GroupModp2048With256BitPrime, + GroupRandomEcp192, + GroupRandomEcp224, + GroupBrainpoolEcp224, + GroupBrainpoolEcp256, + GroupBrainpoolEcp384, + GroupBrainpoolEcp512, + // Life Type + LifeTypeSeconds, + LifeTypeKilobytes, +} + +impl fmt::Display for AttributeValue { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Clone)] +pub struct SaAttribute { + pub attribute_format: u8, + pub attribute_type: AttributeType, + pub attribute_value: AttributeValue, + pub numeric_value: Option<u32>, + pub hex_value: Option<String>, +} + +pub fn parse_isakmp_header(i: &[u8]) -> IResult<&[u8], IsakmpHeader> { + let (i, init_spi) = be_u64(i)?; + let (i, resp_spi) = be_u64(i)?; + let (i, next_payload) = be_u8(i)?; + let (i, vers_byte) = be_u8(i)?; + let vers = (vers_byte >> 4, vers_byte & 0b1111); + let (i, exch_type) = be_u8(i)?; + let (i, flags) = be_u8(i)?; + let (i, msg_id) = be_u32(i)?; + let (i, length) = be_u32(i)?; + let hdr = IsakmpHeader { + init_spi, + resp_spi, + next_payload, + maj_ver: vers.0, + min_ver: vers.1, + exch_type, + flags, + msg_id, + length, + }; + Ok((i, hdr)) +} + +pub fn parse_security_association(i: &[u8]) -> IResult<&[u8], SecurityAssociationPayload> { + let start_i = i; + let (i, domain_of_interpretation) = be_u32(i)?; + let (i, situation) = cond(domain_of_interpretation == 1, take(4_usize))(i)?; + let (i, data) = cond(domain_of_interpretation == 1 && start_i.len() >= 8, |b| { + take(start_i.len() - 8)(b) + })(i)?; + Ok(( + i, + SecurityAssociationPayload { + domain_of_interpretation, + situation, + data, + }, + )) +} + +pub fn parse_key_exchange(i: &[u8], length: u16) -> IResult<&[u8], KeyExchangePayload> { + let (i, key_exchange_data) = take(length as usize)(i)?; + Ok((i, KeyExchangePayload { key_exchange_data })) +} + +pub fn parse_proposal(i: &[u8]) -> IResult<&[u8], ProposalPayload> { + let start_i = i; + let (i, proposal_number) = be_u8(i)?; + let (i, proposal_type) = be_u8(i)?; + let (i, spi_size) = be_u8(i)?; + let (i, number_transforms) = be_u8(i)?; + let (i, spi) = take(spi_size as usize)(i)?; + let (i, payload_data) = cond((start_i.len() - 4) >= spi_size.into(), |b| { + take((start_i.len() - 4) - spi_size as usize)(b) + })(i)?; + let payload = ProposalPayload { + proposal_number, + proposal_type, + spi_size, + number_transforms, + spi, + data: payload_data.unwrap_or_default(), + }; + Ok((i, payload)) +} + +pub fn parse_transform(i: &[u8], length: u16) -> IResult<&[u8], TransformPayload> { + let (i, transform_number) = be_u8(i)?; + let (i, transform_type) = be_u8(i)?; + let (i, _) = be_u16(i)?; + let (i, payload_data) = cond(length >= 4, |b| take(length - 4)(b))(i)?; + Ok(( + i, + TransformPayload { + transform_number, + transform_type, + sa_attributes: payload_data.unwrap_or_default(), + }, + )) +} + +pub fn parse_vendor_id(i: &[u8], length: u16) -> IResult<&[u8], VendorPayload> { + map(take(length), |v| VendorPayload { vendor_id: v })(i) +} + +fn get_attribute_type(v: u16) -> AttributeType { + match v { + 1 => AttributeType::EncryptionAlgorithm, + 2 => AttributeType::HashAlgorithm, + 3 => AttributeType::AuthenticationMethod, + 4 => AttributeType::GroupDescription, + 5 => AttributeType::GroupType, + 6 => AttributeType::GroupPrime, + 7 => AttributeType::GroupGeneratorOne, + 8 => AttributeType::GroupGeneratorTwo, + 9 => AttributeType::GroupCurveA, + 10 => AttributeType::GroupCurveB, + 11 => AttributeType::LifeType, + 12 => AttributeType::LifeDuration, + 13 => AttributeType::Prf, + 14 => AttributeType::KeyLength, + 15 => AttributeType::FieldSize, + 16 => AttributeType::GroupOrder, + _ => AttributeType::Unknown, + } +} + +fn get_encryption_algorithm(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::EncDesCbc, + 2 => AttributeValue::EncIdeaCbc, + 3 => AttributeValue::EncBlowfishCbc, + 4 => AttributeValue::EncRc5R16B64Cbc, + 5 => AttributeValue::EncTripleDesCbc, + 6 => AttributeValue::EncCastCbc, + 7 => AttributeValue::EncAesCbc, + 8 => AttributeValue::EncCamelliaCbc, + _ => AttributeValue::Unknown, + } +} + +fn get_hash_algorithm(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::HashMd5, + 2 => AttributeValue::HashSha, + 3 => AttributeValue::HashTiger, + 4 => AttributeValue::HashSha2_256, + 5 => AttributeValue::HashSha2_384, + 6 => AttributeValue::HashSha2_512, + _ => AttributeValue::Unknown, + } +} + +fn get_authentication_method(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::AuthPreSharedKey, + 2 => AttributeValue::AuthDssSignatures, + 3 => AttributeValue::AuthRsaSignatures, + 4 => AttributeValue::AuthEncryptionWithRsa, + 5 => AttributeValue::AuthRevisedEncryptionWithRsa, + 6 => AttributeValue::AuthReserved, + 7 => AttributeValue::AuthReserved, + 8 => AttributeValue::AuthReserved, + 9 => AttributeValue::AuthEcdsaSha256, + 10 => AttributeValue::AuthEcdsaSha384, + 11 => AttributeValue::AuthEcdsaSha512, + _ => AttributeValue::Unknown, + } +} + +fn get_group_description(v: u16) -> AttributeValue { + match v { + 1 => AttributeValue::GroupDefault768BitModp, + 2 => AttributeValue::GroupAlternate1024BitModpGroup, + 3 => AttributeValue::GroupEc2nOnGp2p155, + 4 => AttributeValue::GroupEc2nOnGp2p185, + 5 => AttributeValue::GroupModp1536Bit, + 6 => AttributeValue::GroupEc2nOverGf2p163, + 7 => AttributeValue::GroupEc2nOverGf2p163, + 8 => AttributeValue::GroupEc2nOverGf2p283, + 9 => AttributeValue::GroupEc2nOverGf2p283, + 10 => AttributeValue::GroupEc2nOverGf2p409, + 11 => AttributeValue::GroupEc2nOverGf2p409, + 12 => AttributeValue::GroupEc2nOverGf2p571, + 13 => AttributeValue::GroupEc2nOverGf2p571, + 14 => AttributeValue::GroupModp2048Bit, + 15 => AttributeValue::GroupModp3072Bit, + 16 => AttributeValue::GroupModp4096Bit, + 17 => AttributeValue::GroupModp6144Bit, + 18 => AttributeValue::GroupModp8192Bit, + 19 => AttributeValue::GroupRandomEcp256, + 20 => AttributeValue::GroupRandomEcp384, + 21 => AttributeValue::GroupRandomEcp521, + 22 => AttributeValue::GroupModp1024With160BitPrime, + 23 => AttributeValue::GroupModp2048With224BitPrime, + 24 => AttributeValue::GroupModp2048With256BitPrime, + 25 => AttributeValue::GroupRandomEcp192, + 26 => AttributeValue::GroupRandomEcp224, + 27 => AttributeValue::GroupBrainpoolEcp224, + 28 => AttributeValue::GroupBrainpoolEcp256, + 29 => AttributeValue::GroupBrainpoolEcp384, + 30 => AttributeValue::GroupBrainpoolEcp512, + _ => AttributeValue::Unknown, + } +} + +pub fn parse_sa_attribute(i: &[u8]) -> IResult<&[u8], Vec<SaAttribute>> { + fn parse_attribute(i: &[u8]) -> IResult<&[u8], SaAttribute> { + let (i, b) = be_u16(i)?; + let format = ((b >> 15) as u8, b & 0x7f_ff); + let (i, attribute_length_or_value) = be_u16(i)?; // depends on format bit) = 1 -> value | 0 -> number of following bytes + let (i, numeric_variable_value) = + cond(format.0 == 0 && attribute_length_or_value == 4, be_u32)(i)?; // interpret as number + let (i, variable_attribute_value) = cond( + format.0 == 0 && attribute_length_or_value != 4, + take(attribute_length_or_value), + )(i)?; + let attr = SaAttribute { + attribute_format: format.0, + attribute_type: get_attribute_type(format.1), + attribute_value: match format.1 { + 1 => get_encryption_algorithm(attribute_length_or_value), + 2 => get_hash_algorithm(attribute_length_or_value), + 3 => get_authentication_method(attribute_length_or_value), + 4 => get_group_description(attribute_length_or_value), + 11 => match attribute_length_or_value { + 1 => AttributeValue::LifeTypeSeconds, + 2 => AttributeValue::LifeTypeKilobytes, + _ => AttributeValue::Unknown, + }, + _ => AttributeValue::Unknown, + }, + numeric_value: match format.0 { + 1 => Some(attribute_length_or_value as u32), + 0 => numeric_variable_value, + _ => None, + }, + hex_value: match format.0 { + 0 => variable_attribute_value + .map(to_hex), + _ => None, + }, + }; + Ok((i, attr)) + } + many0(complete(parse_attribute))(i) +} + +pub fn parse_nonce(i: &[u8], length: u16) -> IResult<&[u8], NoncePayload> { + map(take(length), |v| NoncePayload { nonce_data: v })(i) +} + +pub fn parse_ikev1_payload_list(i: &[u8]) -> IResult<&[u8], Vec<IsakmpPayload>> { + fn parse_payload(i: &[u8]) -> IResult<&[u8], IsakmpPayload> { + let (i, next_payload) = be_u8(i)?; + let (i, reserved) = be_u8(i)?; + let (i, payload_length) = be_u16(i)?; + let (i, payload_data) = cond(payload_length >= 4, |b| take(payload_length - 4)(b))(i)?; + Ok(( + i, + IsakmpPayload { + payload_header: IsakmpPayloadHeader { + next_payload, + reserved, + payload_length, + }, + data: payload_data.unwrap_or_default(), + }, + )) + } + many0(complete(parse_payload))(i) +} + +#[derive(FromPrimitive, Debug)] +pub enum IsakmpPayloadType { + None = 0, + SecurityAssociation = 1, + Proposal = 2, + Transform = 3, + KeyExchange = 4, + Identification = 5, + Certificate = 6, + CertificateRequest = 7, + Hash = 8, + Signature = 9, + Nonce = 10, + Notification = 11, + Delete = 12, + VendorID = 13, + SaKekPayload = 15, + SaTekPayload = 16, + KeyDownload = 17, + SequenceNumber = 18, + ProofOfPossession = 19, + NatDiscovery = 20, + NatOriginalAddress = 21, + GroupAssociatedPolicy = 22, +} + +impl fmt::Display for IsakmpPayloadType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +pub fn parse_payload( + payload_type: u8, data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>, + key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>, + vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>, +) -> Result<(), ()> { + payload_types.insert(payload_type); + + let element = num::FromPrimitive::from_u8(payload_type); + match element { + Some(IsakmpPayloadType::SecurityAssociation) => { + if parse_security_association_payload( + data, + data_length, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ).is_err() { + SCLogDebug!("Error parsing SecurityAssociation"); + return Err(()); + } + Ok(()) + } + Some(IsakmpPayloadType::Proposal) => { + if parse_proposal_payload( + data, + data_length, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ).is_err() { + SCLogDebug!("Error parsing Proposal"); + return Err(()); + } + Ok(()) + } + Some(IsakmpPayloadType::Transform) => { + if let Ok((_rem, payload)) = parse_transform(data, data_length) { + if let Ok((_, attribute_list)) = parse_sa_attribute(payload.sa_attributes) { + transforms.push(attribute_list); + } + } + Ok(()) + } + Some(IsakmpPayloadType::KeyExchange) => { + let res = parse_key_exchange(data, data_length); + if let Ok((_rem, payload)) = res { + *key_exchange = Vec::from(payload.key_exchange_data); + } + Ok(()) + } + Some(IsakmpPayloadType::Nonce) => { + let res = parse_nonce(data, data_length); + if let Ok((_rem, payload)) = res { + *nonce = Vec::from(payload.nonce_data); + } + Ok(()) + } + Some(IsakmpPayloadType::VendorID) => { + let res = parse_vendor_id(data, data_length); + if let Ok((_rem, payload)) = res { + vendor_ids.push(to_hex(payload.vendor_id)); + } + Ok(()) + } + _ => Ok(()), + } +} + +fn parse_proposal_payload( + data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>, + key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>, + vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>, +) -> Result<(), ()> { + match parse_proposal(&data[0..data_length as usize]) { + Ok((_rem, payload)) => { + let mut cur_payload_type = IsakmpPayloadType::Transform as u8; + match parse_ikev1_payload_list(payload.data) { + Ok((_, payload_list)) => { + for isakmp_payload in payload_list { + if parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ).is_err() { + SCLogDebug!("Error parsing transform payload"); + return Err(()); + } + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + Ok(()) + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data parsing payload list"); + Err(()) + } + Err(_) => { + SCLogDebug!("Error parsing payload list"); + Err(()) + } + } + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data"); + Err(()) + } + Err(_) => Err(()), + } +} + +fn parse_security_association_payload( + data: &[u8], data_length: u16, domain_of_interpretation: &mut Option<u32>, + key_exchange: &mut Vec<u8>, nonce: &mut Vec<u8>, transforms: &mut Vec<Vec<SaAttribute>>, + vendor_ids: &mut Vec<String>, payload_types: &mut HashSet<u8>, +) -> Result<(), ()> { + match parse_security_association(&data[0..data_length as usize]) { + Ok((_rem, payload)) => { + *domain_of_interpretation = Some(payload.domain_of_interpretation); + if payload.domain_of_interpretation == 1 { + // 1 is assigned to IPsec DOI + let mut cur_payload_type = IsakmpPayloadType::Proposal as u8; + if let Some(p_data) = payload.data { + match parse_ikev1_payload_list(p_data) { + Ok((_, payload_list)) => { + for isakmp_payload in payload_list { + if parse_payload( + cur_payload_type, + isakmp_payload.data, + isakmp_payload.data.len() as u16, + domain_of_interpretation, + key_exchange, + nonce, + transforms, + vendor_ids, + payload_types, + ).is_err() { + SCLogDebug!("Error parsing proposal payload"); + return Err(()); + } + cur_payload_type = isakmp_payload.payload_header.next_payload; + } + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data parsing payload list"); + return Err(()); + } + Err(_) => { + SCLogDebug!("Error parsing payload list"); + return Err(()); + } + } + } + } + Ok(()) + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("Incomplete data"); + Err(()) + } + Err(_) => Err(()), + } +} diff --git a/rust/src/jsonbuilder.rs b/rust/src/jsonbuilder.rs new file mode 100644 index 0000000..9ff6234 --- /dev/null +++ b/rust/src/jsonbuilder.rs @@ -0,0 +1,1363 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + + //! Module for building JSON documents. + +#![allow(clippy::missing_safety_doc)] + +use std::cmp::max; +use std::collections::TryReserveError; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::str::Utf8Error; + +const INIT_SIZE: usize = 4096; + +#[derive(Debug, PartialEq, Eq)] +pub enum JsonError { + InvalidState, + Utf8Error(Utf8Error), + Memory, +} + +impl std::error::Error for JsonError {} + +impl std::fmt::Display for JsonError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + JsonError::InvalidState => write!(f, "invalid state"), + JsonError::Utf8Error(ref e) => e.fmt(f), + JsonError::Memory => write!(f, "memory error"), + } + } +} + +impl From<TryReserveError> for JsonError { + fn from(_: TryReserveError) -> Self { + JsonError::Memory + } +} + +impl From<Utf8Error> for JsonError { + fn from(e: Utf8Error) -> Self { + JsonError::Utf8Error(e) + } +} + +#[derive(Clone, Debug)] +enum Type { + Object, + Array, +} + +#[derive(Debug, Clone, Copy, PartialEq)] +#[repr(C)] +enum State { + None = 0, + ObjectFirst, + ObjectNth, + ArrayFirst, + ArrayNth, +} + +impl State { + fn from_u64(v: u64) -> Result<State, JsonError> { + let s = match v { + 0 => State::None, + 1 => State::ObjectFirst, + 2 => State::ObjectNth, + 3 => State::ArrayFirst, + 4 => State::ArrayNth, + _ => { + return Err(JsonError::InvalidState); + } + }; + Ok(s) + } +} + +/// A "mark" or saved state for a JsonBuilder object. +/// +/// The name is full, and the types are u64 as this object is used +/// directly in C as well. +#[repr(C)] +pub struct JsonBuilderMark { + position: u64, + state_index: u64, + state: u64, +} + +#[derive(Debug, Clone)] +pub struct JsonBuilder { + buf: String, + state: Vec<State>, + init_type: Type, +} + +impl JsonBuilder { + /// Returns a new JsonBuilder in object state. + pub fn try_new_object() -> Result<Self, JsonError> { + Self::try_new_object_with_capacity(INIT_SIZE) + } + + pub fn try_new_object_with_capacity(capacity: usize) -> Result<Self, JsonError> { + let mut buf = String::new(); + buf.try_reserve(capacity)?; + buf.push('{'); + let mut state = Vec::new(); + state.try_reserve(32)?; + state.extend_from_slice(&[State::None, State::ObjectFirst]); + Ok(Self { + buf, + state, + init_type: Type::Object, + }) + } + + /// Returns a new JsonBuilder in array state. + pub fn try_new_array() -> Result<Self, JsonError> { + Self::try_new_array_with_capacity(INIT_SIZE) + } + + pub fn try_new_array_with_capacity(capacity: usize) -> Result<Self, JsonError> { + let mut buf = String::new(); + buf.try_reserve(capacity)?; + buf.push('['); + let mut state = Vec::new(); + state.try_reserve(32)?; + state.extend_from_slice(&[State::None, State::ArrayFirst]); + Ok(Self { + buf, + state, + init_type: Type::Array, + }) + } + + /// A wrapper around String::push that pre-allocates data return + /// an error if unable to. + pub fn push(&mut self, ch: char) -> Result<&mut Self, JsonError> { + if self.buf.capacity() == self.buf.len() { + self.buf.try_reserve(INIT_SIZE)?; + } + self.buf.push(ch); + Ok(self) + } + + /// A wrapper around String::push_str that pre-allocates data + /// return an error if unable to. + pub fn push_str(&mut self, s: &str) -> Result<&mut Self, JsonError> { + if self.buf.capacity() < self.buf.len() + s.len() { + self.buf.try_reserve(max(INIT_SIZE, s.len()))?; + } + self.buf.push_str(s); + Ok(self) + } + + // Reset the builder to its initial state, without losing + // the current capacity. + pub fn reset(&mut self) { + self.buf.truncate(0); + self.state.clear(); + match self.init_type { + Type::Array => { + self.buf.push('['); + self.state + .extend_from_slice(&[State::None, State::ArrayFirst]); + } + Type::Object => { + self.buf.push('{'); + self.state + .extend_from_slice(&[State::None, State::ObjectFirst]); + } + } + } + + // Closes the currently open datatype (object or array). + pub fn close(&mut self) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst | State::ObjectNth => { + self.push('}')?; + self.pop_state(); + Ok(self) + } + State::ArrayFirst | State::ArrayNth => { + self.push(']')?; + self.pop_state(); + Ok(self) + } + State::None => { + debug_validate_fail!("invalid state"); + Err(JsonError::InvalidState) + } + } + } + + // Return the current state of the JsonBuilder. + fn current_state(&self) -> State { + if self.state.is_empty() { + State::None + } else { + self.state[self.state.len() - 1] + } + } + + /// Move to a new state. + fn push_state(&mut self, state: State) -> Result<(), JsonError> { + if self.state.len() == self.state.capacity() { + self.state.try_reserve(32)?; + } + self.state.push(state); + Ok(()) + } + + /// Go back to the previous state. + fn pop_state(&mut self) { + self.state.pop(); + } + + /// Change the current state. + fn set_state(&mut self, state: State) { + let n = self.state.len() - 1; + self.state[n] = state; + } + + pub fn get_mark(&self) -> JsonBuilderMark { + JsonBuilderMark { + position: self.buf.len() as u64, + state: self.current_state() as u64, + state_index: self.state.len() as u64, + } + } + + pub fn restore_mark(&mut self, mark: &JsonBuilderMark) -> Result<(), JsonError> { + let state = State::from_u64(mark.state)?; + if mark.position < (self.buf.len() as u64) && mark.state_index < (self.state.len() as u64) { + self.buf.truncate(mark.position as usize); + self.state.truncate(mark.state_index as usize); + self.state[(mark.state_index as usize) - 1] = state; + } + Ok(()) + } + + /// Open an object under the given key. + /// + /// For example: + /// Before: { + /// After: {"key": { + pub fn open_object(&mut self, key: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst => { + self.push('"')?; + self.set_state(State::ObjectNth); + } + State::ObjectNth => { + self.push_str(",\"")?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push_str(key)?; + self.push_str("\":{")?; + self.push_state(State::ObjectFirst)?; + Ok(self) + } + + /// Start an object. + /// + /// Like open_object but does not create the object under a key. An + /// error will be returned if starting an object does not make + /// sense for the current state. + pub fn start_object(&mut self) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => {} + State::ArrayNth => { + self.push(',')?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('{')?; + self.set_state(State::ArrayNth); + self.push_state(State::ObjectFirst)?; + Ok(self) + } + + /// Open an array under the given key. + /// + /// For example: + /// Before: { + /// After: {"key": [ + pub fn open_array(&mut self, key: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectFirst => {} + State::ObjectNth => { + self.push(',')?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":[")?; + self.set_state(State::ObjectNth); + self.push_state(State::ArrayFirst)?; + Ok(self) + } + + /// Add a string to an array. + pub fn append_string(&mut self, val: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.encode_string(val)?; + self.set_state(State::ArrayNth); + Ok(self) + } + State::ArrayNth => { + self.push(',')?; + self.encode_string(val)?; + Ok(self) + } + _ => { + debug_validate_fail!("invalid state"); + Err(JsonError::InvalidState) + } + } + } + + pub fn append_string_from_bytes(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { + match std::str::from_utf8(val) { + Ok(s) => self.append_string(s), + Err(_) => self.append_string(&try_string_from_bytes(val)?), + } + } + + /// Add a string to an array. + pub fn append_base64(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.push('"')?; + self.encode_base64(val)?; + self.push('"')?; + self.set_state(State::ArrayNth); + Ok(self) + } + State::ArrayNth => { + self.push(',')?; + self.push('"')?; + self.encode_base64(val)?; + self.push('"')?; + Ok(self) + } + _ => { + debug_validate_fail!("invalid state"); + Err(JsonError::InvalidState) + } + } + } + + /// Add a byte array to a JSON array encoded as hex. + pub fn append_hex(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.push('"')?; + for i in 0..val.len() { + self.push(HEX[(val[i] >> 4) as usize] as char)?; + self.push(HEX[(val[i] & 0xf) as usize] as char)?; + } + self.push('"')?; + self.set_state(State::ArrayNth); + Ok(self) + } + State::ArrayNth => { + self.push(',')?; + self.push('"')?; + for i in 0..val.len() { + self.push(HEX[(val[i] >> 4) as usize] as char)?; + self.push(HEX[(val[i] & 0xf) as usize] as char)?; + } + self.push('"')?; + Ok(self) + } + _ => { + debug_validate_fail!("invalid state"); + Err(JsonError::InvalidState) + } + } + } + + /// Add an unsigned integer to an array. + pub fn append_uint(&mut self, val: u64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.set_state(State::ArrayNth); + } + State::ArrayNth => { + self.push(',')?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push_str(&val.to_string()) + } + + pub fn append_float(&mut self, val: f64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.set_state(State::ArrayNth); + } + State::ArrayNth => { + self.push(',')?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push_str(&val.to_string())?; + Ok(self) + } + + pub fn set_object(&mut self, key: &str, js: &JsonBuilder) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":")?; + self.push_str(&js.buf)?; + Ok(self) + } + + /// Append an object onto this array. + /// + /// '[' -> '[{...}' + /// '[{...}' -> '[{...},{...}' + pub fn append_object(&mut self, js: &JsonBuilder) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ArrayFirst => { + self.set_state(State::ArrayNth); + } + State::ArrayNth => { + self.push(',')?; + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push_str(&js.buf)?; + Ok(self) + } + + /// Set a key and string value type on an object. + #[inline(always)] + pub fn set_string(&mut self, key: &str, val: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":")?; + self.encode_string(val)?; + Ok(self) + } + + pub fn set_formatted(&mut self, formatted: &str) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push_str(formatted)?; + Ok(self) + } + + /// Set a key and a string value (from bytes) on an object. + pub fn set_string_from_bytes(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> { + match std::str::from_utf8(val) { + Ok(s) => self.set_string(key, s), + Err(_) => self.set_string(key, &try_string_from_bytes(val)?), + } + } + + /// Set a key and a string field as the base64 encoded string of the value. + pub fn set_base64(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":\"")?; + self.encode_base64(val)?; + self.push('"')?; + + Ok(self) + } + + /// Set a key and a string field as the hex encoded string of the value. + pub fn set_hex(&mut self, key: &str, val: &[u8]) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":\"")?; + for i in 0..val.len() { + self.push(HEX[(val[i] >> 4) as usize] as char)?; + self.push(HEX[(val[i] & 0xf) as usize] as char)?; + } + self.push('"')?; + + Ok(self) + } + + /// Set a key and an unsigned integer type on an object. + pub fn set_uint(&mut self, key: &str, val: u64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":")?; + self.push_str(&val.to_string())?; + Ok(self) + } + + /// Set a key and a signed integer type on an object. + pub fn set_int(&mut self, key: &str, val: i64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":")?; + self.push_str(&val.to_string())?; + Ok(self) + } + + pub fn set_float(&mut self, key: &str, val: f64) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + self.push_str("\":")?; + self.push_str(&val.to_string())?; + Ok(self) + } + + pub fn set_bool(&mut self, key: &str, val: bool) -> Result<&mut Self, JsonError> { + match self.current_state() { + State::ObjectNth => { + self.push(',')?; + } + State::ObjectFirst => { + self.set_state(State::ObjectNth); + } + _ => { + debug_validate_fail!("invalid state"); + return Err(JsonError::InvalidState); + } + } + self.push('"')?; + self.push_str(key)?; + if val { + self.push_str("\":true")?; + } else { + self.push_str("\":false")?; + } + Ok(self) + } + + pub fn capacity(&self) -> usize { + self.buf.capacity() + } + + /// Encode a string into the buffer, escaping as needed. + /// + /// The string is encoded into an intermediate vector as its faster + /// than building onto the buffer. + /// + /// TODO: Revisit this, would be nice to build directly onto the + /// existing buffer. + #[inline(always)] + fn encode_string(&mut self, val: &str) -> Result<(), JsonError> { + let mut buf = Vec::new(); + + // Start by allocating a reasonable size buffer, it will be + // grown if needed. + buf.try_reserve(val.len() * 2 + 2)?; + buf.resize(val.len() * 2 + 2, 0); + + let mut offset = 0; + let bytes = val.as_bytes(); + buf[offset] = b'"'; + offset += 1; + for &x in bytes.iter() { + if offset + 7 >= buf.capacity() { + // We could be smarter, but just double the buffer size. + buf.try_reserve(buf.capacity())?; + buf.resize(buf.capacity(), 0); + } + let escape = ESCAPED[x as usize]; + if escape == 0 { + buf[offset] = x; + offset += 1; + } else if escape == b'u' { + buf[offset] = b'\\'; + offset += 1; + buf[offset] = b'u'; + offset += 1; + buf[offset] = b'0'; + offset += 1; + buf[offset] = b'0'; + offset += 1; + buf[offset] = HEX[(x >> 4 & 0xf) as usize]; + offset += 1; + buf[offset] = HEX[(x & 0xf) as usize]; + offset += 1; + } else { + buf[offset] = b'\\'; + offset += 1; + buf[offset] = escape; + offset += 1; + } + } + buf[offset] = b'"'; + offset += 1; + match std::str::from_utf8(&buf[0..offset]) { + Ok(s) => { + self.push_str(s)?; + } + Err(err) => { + let error = format!( + "\"UTF8-ERROR: what=[escaped string] error={} output={:02x?} input={:02x?}\"", + err, + &buf[0..offset], + val.as_bytes(), + ); + self.push_str(&error)?; + } + } + Ok(()) + } + + fn encode_base64(&mut self, val: &[u8]) -> Result<&mut Self, JsonError> { + let encoded_len = 4 * ((val.len() + 2) / 3); + if self.buf.capacity() < self.buf.len() + encoded_len { + self.buf.try_reserve(encoded_len)?; + } + base64::encode_config_buf(val, base64::STANDARD, &mut self.buf); + Ok(self) + } +} + +/// A Suricata specific function to create a string from bytes when UTF-8 decoding fails. +/// +/// For bytes over 0x0f, we encode as hex like "\xf2". +fn try_string_from_bytes(input: &[u8]) -> Result<String, JsonError> { + let mut out = String::new(); + + // Allocate enough data to handle the worst case scenario of every + // byte needing to be presented as a byte. + out.try_reserve(input.len() * 4)?; + + for b in input.iter() { + if *b < 128 { + out.push(*b as char); + } else { + out.push_str(&format!("\\x{:02x}", *b)); + } + } + return Ok(out); +} + +#[no_mangle] +pub extern "C" fn jb_new_object() -> *mut JsonBuilder { + match JsonBuilder::try_new_object() { + Ok(js) => { + let boxed = Box::new(js); + Box::into_raw(boxed) + } + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn jb_new_array() -> *mut JsonBuilder { + match JsonBuilder::try_new_array() { + Ok(js) => { + let boxed = Box::new(js); + Box::into_raw(boxed) + } + Err(_) => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn jb_clone(js: &mut JsonBuilder) -> *mut JsonBuilder { + let clone = Box::new(js.clone()); + Box::into_raw(clone) +} + +#[no_mangle] +pub unsafe extern "C" fn jb_free(js: &mut JsonBuilder) { + let _ = Box::from_raw(js); +} + +#[no_mangle] +pub extern "C" fn jb_capacity(jb: &mut JsonBuilder) -> usize { + jb.capacity() +} + +#[no_mangle] +pub extern "C" fn jb_reset(jb: &mut JsonBuilder) { + jb.reset(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_open_object(js: &mut JsonBuilder, key: *const c_char) -> bool { + if let Ok(s) = CStr::from_ptr(key).to_str() { + js.open_object(s).is_ok() + } else { + false + } +} + +#[no_mangle] +pub unsafe extern "C" fn jb_start_object(js: &mut JsonBuilder) -> bool { + js.start_object().is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_open_array(js: &mut JsonBuilder, key: *const c_char) -> bool { + if let Ok(s) = CStr::from_ptr(key).to_str() { + js.open_array(s).is_ok() + } else { + false + } +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_string( + js: &mut JsonBuilder, key: *const c_char, val: *const c_char, +) -> bool { + if val.is_null() { + return false; + } + if let Ok(key) = CStr::from_ptr(key).to_str() { + if let Ok(val) = CStr::from_ptr(val).to_str() { + return js.set_string(key, val).is_ok(); + } + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_string_from_bytes( + js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32, +) -> bool { + if bytes.is_null() || len == 0 { + return false; + } + if let Ok(key) = CStr::from_ptr(key).to_str() { + let val = std::slice::from_raw_parts(bytes, len as usize); + return js.set_string_from_bytes(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_base64( + js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32, +) -> bool { + if bytes.is_null() || len == 0 { + return false; + } + if let Ok(key) = CStr::from_ptr(key).to_str() { + let val = std::slice::from_raw_parts(bytes, len as usize); + return js.set_base64(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_hex( + js: &mut JsonBuilder, key: *const c_char, bytes: *const u8, len: u32, +) -> bool { + if bytes.is_null() || len == 0 { + return false; + } + if let Ok(key) = CStr::from_ptr(key).to_str() { + let val = std::slice::from_raw_parts(bytes, len as usize); + return js.set_hex(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_formatted(js: &mut JsonBuilder, formatted: *const c_char) -> bool { + if let Ok(formatted) = CStr::from_ptr(formatted).to_str() { + return js.set_formatted(formatted).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_object(jb: &mut JsonBuilder, obj: &JsonBuilder) -> bool { + jb.append_object(obj).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_object( + js: &mut JsonBuilder, key: *const c_char, val: &mut JsonBuilder, +) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_object(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_string(js: &mut JsonBuilder, val: *const c_char) -> bool { + if val.is_null() { + return false; + } + if let Ok(val) = CStr::from_ptr(val).to_str() { + return js.append_string(val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_string_from_bytes( + js: &mut JsonBuilder, bytes: *const u8, len: u32, +) -> bool { + if bytes.is_null() || len == 0 { + return false; + } + let val = std::slice::from_raw_parts(bytes, len as usize); + return js.append_string_from_bytes(val).is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_base64( + js: &mut JsonBuilder, bytes: *const u8, len: u32, +) -> bool { + if bytes.is_null() || len == 0 { + return false; + } + let val = std::slice::from_raw_parts(bytes, len as usize); + return js.append_base64(val).is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_uint(js: &mut JsonBuilder, val: u64) -> bool { + return js.append_uint(val).is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_append_float(js: &mut JsonBuilder, val: f64) -> bool { + return js.append_float(val).is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_uint(js: &mut JsonBuilder, key: *const c_char, val: u64) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_uint(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_int(js: &mut JsonBuilder, key: *const c_char, val: i64) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_int(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_float(js: &mut JsonBuilder, key: *const c_char, val: f64) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_float(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_set_bool(js: &mut JsonBuilder, key: *const c_char, val: bool) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + return js.set_bool(key, val).is_ok(); + } + return false; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_close(js: &mut JsonBuilder) -> bool { + js.close().is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_len(js: &JsonBuilder) -> usize { + js.buf.len() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_ptr(js: &mut JsonBuilder) -> *const u8 { + js.buf.as_ptr() +} + +#[no_mangle] +pub unsafe extern "C" fn jb_get_mark(js: &mut JsonBuilder, mark: &mut JsonBuilderMark) { + let m = js.get_mark(); + mark.position = m.position; + mark.state_index = m.state_index; + mark.state = m.state; +} + +#[no_mangle] +pub unsafe extern "C" fn jb_restore_mark(js: &mut JsonBuilder, mark: &mut JsonBuilderMark) -> bool { + js.restore_mark(mark).is_ok() +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_try_reserve() { + // Just a sanity check that try_reserve works as I expect. + let mut buf = String::new(); + assert_eq!(buf.len(), 0); + assert_eq!(buf.capacity(), 0); + + buf.try_reserve(1).unwrap(); + assert_eq!(buf.len(), 0); + assert!(buf.capacity() >= 1); + } + + #[test] + fn test_set_bool() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_bool("first", true).unwrap(); + assert_eq!(jb.buf, r#"{"first":true"#); + jb.set_bool("second", false).unwrap(); + assert_eq!(jb.buf, r#"{"first":true,"second":false"#); + + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_bool("first", false).unwrap(); + assert_eq!(jb.buf, r#"{"first":false"#); + jb.set_bool("second", true).unwrap(); + assert_eq!(jb.buf, r#"{"first":false,"second":true"#); + } + + #[test] + fn test_object_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::try_new_object().unwrap(); + + js.open_object("object")?; + assert_eq!(js.current_state(), State::ObjectFirst); + assert_eq!(js.buf, r#"{"object":{"#); + + js.set_string("one", "one")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"object":{"one":"one""#); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"object":{"one":"one"}"#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"object":{"one":"one"}}"#); + + Ok(()) + } + + #[test] + fn test_empty_array_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::try_new_object().unwrap(); + + js.open_array("array")?; + assert_eq!(js.current_state(), State::ArrayFirst); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"array":[]"#); + + js.close()?; + assert_eq!(js.buf, r#"{"array":[]}"#); + + Ok(()) + } + + #[test] + #[cfg(not(feature = "debug-validate"))] + fn test_array_in_object() -> Result<(), JsonError> { + let mut js = JsonBuilder::try_new_object().unwrap(); + + // Attempt to add an item, should fail. + assert_eq!( + js.append_string("will fail").err().unwrap(), + JsonError::InvalidState + ); + + js.open_array("array")?; + assert_eq!(js.current_state(), State::ArrayFirst); + + js.append_string("one")?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one""#); + + js.append_string("two")?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one","two""#); + + js.append_uint(3)?; + assert_eq!(js.current_state(), State::ArrayNth); + assert_eq!(js.buf, r#"{"array":["one","two",3"#); + + js.close()?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"array":["one","two",3]"#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"array":["one","two",3]}"#); + + Ok(()) + } + + #[test] + fn basic_test() -> Result<(), JsonError> { + let mut js = JsonBuilder::try_new_object().unwrap(); + assert_eq!(js.current_state(), State::ObjectFirst); + assert_eq!(js.buf, "{"); + + js.set_string("one", "one")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"one":"one""#); + + js.set_string("two", "two")?; + assert_eq!(js.current_state(), State::ObjectNth); + assert_eq!(js.buf, r#"{"one":"one","two":"two""#); + + js.close()?; + assert_eq!(js.current_state(), State::None); + assert_eq!(js.buf, r#"{"one":"one","two":"two"}"#); + + Ok(()) + } + + #[test] + fn test_combine() -> Result<(), JsonError> { + let mut main = JsonBuilder::try_new_object().unwrap(); + let mut obj = JsonBuilder::try_new_object().unwrap(); + obj.close()?; + + let mut array = JsonBuilder::try_new_array().unwrap(); + array.append_string("one")?; + array.append_uint(2)?; + array.close()?; + main.set_object("object", &obj)?; + main.set_object("array", &array)?; + main.close()?; + + assert_eq!(main.buf, r#"{"object":{},"array":["one",2]}"#); + + Ok(()) + } + + #[test] + fn test_objects_in_array() -> Result<(), JsonError> { + let mut js = JsonBuilder::try_new_array()?; + assert_eq!(js.buf, r#"["#); + + js.start_object()?; + assert_eq!(js.buf, r#"[{"#); + + js.set_string("uid", "0")?; + assert_eq!(js.buf, r#"[{"uid":"0""#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"}"#); + + js.start_object()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"#); + + js.set_string("username", "root")?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root""#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}"#); + + js.close()?; + assert_eq!(js.buf, r#"[{"uid":"0"},{"username":"root"}]"#); + + Ok(()) + } + + #[test] + fn test_grow() -> Result<(), JsonError> { + let mut jb = JsonBuilder::try_new_object_with_capacity(1).unwrap(); + + // For efficiency reasons, more capacity may be allocated than + // requested. + assert!(jb.capacity() > 0); + let capacity = jb.capacity(); + + let mut value = String::new(); + for i in 0..capacity { + value.push_str((i % 10).to_string().as_str()); + } + jb.set_string("foo", &value)?; + assert!(jb.capacity() > capacity); + Ok(()) + } + + #[test] + fn test_reset() -> Result<(), JsonError> { + let mut jb = JsonBuilder::try_new_object().unwrap(); + assert_eq!(jb.buf, "{"); + jb.set_string("foo", "bar")?; + let cap = jb.capacity(); + jb.reset(); + assert_eq!(jb.buf, "{"); + assert_eq!(jb.capacity(), cap); + Ok(()) + } + + #[test] + fn test_append_string_from_bytes() -> Result<(), JsonError> { + let mut jb = JsonBuilder::try_new_array().unwrap(); + let s = &[0x41, 0x41, 0x41, 0x00]; + jb.append_string_from_bytes(s)?; + assert_eq!(jb.buf, r#"["AAA\u0000""#); + + let s = &[0x00, 0x01, 0x02, 0x03]; + let mut jb = JsonBuilder::try_new_array().unwrap(); + jb.append_string_from_bytes(s)?; + assert_eq!(jb.buf, r#"["\u0000\u0001\u0002\u0003""#); + + Ok(()) + } + + #[test] + fn test_set_string_from_bytes() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_string_from_bytes("first", &[]).unwrap(); + assert_eq!(jb.buf, r#"{"first":"""#); + jb.set_string_from_bytes("second", &[]).unwrap(); + assert_eq!(jb.buf, r#"{"first":"","second":"""#); + } + + #[test] + fn test_append_string_from_bytes_grow() -> Result<(), JsonError> { + let s = &[0x00, 0x01, 0x02, 0x03]; + let mut jb = JsonBuilder::try_new_array().unwrap(); + jb.append_string_from_bytes(s)?; + + for i in 1..1000 { + let mut s = Vec::new(); + s.resize(i, 0x41); + let mut jb = JsonBuilder::try_new_array().unwrap(); + jb.append_string_from_bytes(&s)?; + } + + Ok(()) + } + + #[test] + fn test_invalid_utf8() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_string_from_bytes("invalid", &[0xf0, 0xf1, 0xf2]) + .unwrap(); + assert_eq!(jb.buf, r#"{"invalid":"\\xf0\\xf1\\xf2""#); + + let mut jb = JsonBuilder::try_new_array().unwrap(); + jb.append_string_from_bytes(&[0xf0, 0xf1, 0xf2]).unwrap(); + assert_eq!(jb.buf, r#"["\\xf0\\xf1\\xf2""#); + } + + #[test] + fn test_marks() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_string("foo", "bar").unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar""#); + assert_eq!(jb.current_state(), State::ObjectNth); + assert_eq!(jb.state.len(), 2); + let mark = jb.get_mark(); + + // Mutate such that states are transitioned. + jb.open_array("bar").unwrap(); + jb.start_object().unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar","bar":[{"#); + assert_eq!(jb.current_state(), State::ObjectFirst); + assert_eq!(jb.state.len(), 4); + + // Restore to mark. + jb.restore_mark(&mark).unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar""#); + assert_eq!(jb.current_state(), State::ObjectNth); + assert_eq!(jb.state.len(), 2); + } + + #[test] + fn test_set_formatted() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_formatted("\"foo\":\"bar\"").unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar""#); + jb.set_formatted("\"bar\":\"foo\"").unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar","bar":"foo""#); + jb.close().unwrap(); + assert_eq!(jb.buf, r#"{"foo":"bar","bar":"foo"}"#); + } + + #[test] + fn test_set_float() { + let mut jb = JsonBuilder::try_new_object().unwrap(); + jb.set_float("one", 1.1).unwrap(); + jb.set_float("two", 2.2).unwrap(); + jb.close().unwrap(); + assert_eq!(jb.buf, r#"{"one":1.1,"two":2.2}"#); + } + + #[test] + fn test_append_float() { + let mut jb = JsonBuilder::try_new_array().unwrap(); + jb.append_float(1.1).unwrap(); + jb.append_float(2.2).unwrap(); + jb.close().unwrap(); + assert_eq!(jb.buf, r#"[1.1,2.2]"#); + } +} + +// Escape table as seen in serde-json (MIT/Apache license) + +const QU: u8 = b'"'; +const BS: u8 = b'\\'; +const BB: u8 = b'b'; +const TT: u8 = b't'; +const NN: u8 = b'n'; +const FF: u8 = b'f'; +const RR: u8 = b'r'; +const UU: u8 = b'u'; +const __: u8 = 0; + +// Look up table for characters that need escaping in a product string +static ESCAPED: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + UU, UU, UU, UU, UU, UU, UU, UU, BB, TT, NN, UU, FF, RR, UU, UU, // 0 + UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, UU, // 1 + __, __, QU, __, __, __, __, __, __, __, __, __, __, __, __, __, // 2 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 3 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 4 + __, __, __, __, __, __, __, __, __, __, __, __, BS, __, __, __, // 5 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 6 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 7 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 8 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // 9 + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // A + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // B + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // C + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // D + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // E + __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, __, // F +]; + +pub static HEX: [u8; 16] = [ + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', b'c', b'd', b'e', b'f', +]; diff --git a/rust/src/kerberos.rs b/rust/src/kerberos.rs new file mode 100644 index 0000000..e7c51cc --- /dev/null +++ b/rust/src/kerberos.rs @@ -0,0 +1,80 @@ +/* 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. + */ + +//! Kerberos parser wrapper module. + +use nom7::IResult; +use nom7::error::{ErrorKind, ParseError}; +use nom7::number::streaming::le_u16; +use der_parser6; +use der_parser6::der::parse_der_oid; +use der_parser6::error::BerError; +use kerberos_parser::krb5::{ApReq, PrincipalName, Realm}; +use kerberos_parser::krb5_parser::parse_ap_req; + +#[derive(Debug)] +pub enum SecBlobError { + NotSpNego, + KrbFmtError, + Ber(BerError), + NomError(ErrorKind), +} + +impl From<BerError> for SecBlobError { + fn from(error: BerError) -> Self { + SecBlobError::Ber(error) + } +} + +impl<I> ParseError<I> for SecBlobError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + SecBlobError::NomError(kind) + } + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + SecBlobError::NomError(kind) + } +} + +#[derive(Debug, PartialEq)] +pub struct Kerberos5Ticket { + pub realm: Realm, + pub sname: PrincipalName, +} + +fn parse_kerberos5_request_do(blob: &[u8]) -> IResult<&[u8], ApReq, SecBlobError> +{ + let (_,b) = der_parser6::parse_der(blob).map_err(nom7::Err::convert)?; + let blob = b.as_slice().or( + Err(nom7::Err::Error(SecBlobError::KrbFmtError)) + )?; + let parser = |i| { + let (i, _base_o) = parse_der_oid(i)?; + let (i, _tok_id) = le_u16(i)?; + let (i, ap_req) = parse_ap_req(i)?; + Ok((i, ap_req)) + }; + parser(blob).map_err(nom7::Err::convert) +} + +pub fn parse_kerberos5_request(blob: &[u8]) -> IResult<&[u8], Kerberos5Ticket, SecBlobError> { + let (rem, req) = parse_kerberos5_request_do(blob)?; + let t = Kerberos5Ticket { + realm: req.ticket.realm, + sname: req.ticket.sname, + }; + return Ok((rem, t)); +} diff --git a/rust/src/krb/detect.rs b/rust/src/krb/detect.rs new file mode 100644 index 0000000..25cce9b --- /dev/null +++ b/rust/src/krb/detect.rs @@ -0,0 +1,331 @@ +/* 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 Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::krb::krb5::{test_weak_encryption, KRB5Transaction}; + +use kerberos_parser::krb5::EncryptionType; + +use nom7::branch::alt; +use nom7::bytes::complete::{is_a, tag, take_while, take_while1}; +use nom7::character::complete::char; +use nom7::combinator::{all_consuming, map_res, opt}; +use nom7::multi::many1; +use nom7::IResult; + +use std::ffi::CStr; + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_tx_get_msgtype(tx: &mut KRB5Transaction, ptr: *mut u32) { + *ptr = tx.msg_type.0; +} + +/// Get error code, if present in transaction +/// Return 0 if error code was filled, else 1 +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_tx_get_errcode(tx: &mut KRB5Transaction, ptr: *mut i32) -> u32 { + match tx.error_code { + Some(ref e) => { + *ptr = e.0; + 0 + } + None => 1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_tx_get_cname( + tx: &mut KRB5Transaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ref s) = tx.cname { + if (i as usize) < s.name_string.len() { + let value = &s.name_string[i as usize]; + *buffer = value.as_ptr(); + *buffer_len = value.len() as u32; + return 1; + } + } + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_tx_get_sname( + tx: &mut KRB5Transaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ref s) = tx.sname { + if (i as usize) < s.name_string.len() { + let value = &s.name_string[i as usize]; + *buffer = value.as_ptr(); + *buffer_len = value.len() as u32; + return 1; + } + } + 0 +} + +const KRB_TICKET_FASTARRAY_SIZE: usize = 256; + +#[derive(Debug)] +pub struct DetectKrb5TicketEncryptionList { + positive: [bool; KRB_TICKET_FASTARRAY_SIZE], + negative: [bool; KRB_TICKET_FASTARRAY_SIZE], + other: Vec<EncryptionType>, +} + +impl Default for DetectKrb5TicketEncryptionList { + fn default() -> Self { + Self::new() + } +} + +impl DetectKrb5TicketEncryptionList { + pub fn new() -> Self { + Self { + positive: [false; KRB_TICKET_FASTARRAY_SIZE], + negative: [false; KRB_TICKET_FASTARRAY_SIZE], + other: Vec::new(), + } + } +} + + +// Suppress large enum variant lint as the LIST is very large compared +// to the boolean variant. +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +pub enum DetectKrb5TicketEncryptionData { + WEAK(bool), + LIST(DetectKrb5TicketEncryptionList), +} + +pub fn detect_parse_encryption_weak(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> { + let (i, neg) = opt(char('!'))(i)?; + let (i, _) = tag("weak")(i)?; + let value = neg.is_none(); + return Ok((i, DetectKrb5TicketEncryptionData::WEAK(value))); +} + +trait MyFromStr { + fn from_str(s: &str) -> Result<Self, String> + where + Self: Sized; +} + +impl MyFromStr for EncryptionType { + fn from_str(s: &str) -> Result<Self, String> { + let su_slice: &str = s; + match su_slice { + "des-cbc-crc" => Ok(EncryptionType::DES_CBC_CRC), + "des-cbc-md4" => Ok(EncryptionType::DES_CBC_MD4), + "des-cbc-md5" => Ok(EncryptionType::DES_CBC_MD5), + "des3-cbc-md5" => Ok(EncryptionType::DES3_CBC_MD5), + "des3-cbc-sha1" => Ok(EncryptionType::DES3_CBC_SHA1), + "dsaWithSHA1-CmsOID" => Ok(EncryptionType::DSAWITHSHA1_CMSOID), + "md5WithRSAEncryption-CmsOID" => Ok(EncryptionType::MD5WITHRSAENCRYPTION_CMSOID), + "sha1WithRSAEncryption-CmsOID" => Ok(EncryptionType::SHA1WITHRSAENCRYPTION_CMSOID), + "rc2CBC-EnvOID" => Ok(EncryptionType::RC2CBC_ENVOID), + "rsaEncryption-EnvOID" => Ok(EncryptionType::RSAENCRYPTION_ENVOID), + "rsaES-OAEP-ENV-OID" => Ok(EncryptionType::RSAES_OAEP_ENV_OID), + "des-ede3-cbc-Env-OID" => Ok(EncryptionType::DES_EDE3_CBC_ENV_OID), + "des3-cbc-sha1-kd" => Ok(EncryptionType::DES3_CBC_SHA1_KD), + "aes128-cts-hmac-sha1-96" => Ok(EncryptionType::AES128_CTS_HMAC_SHA1_96), + "aes256-cts-hmac-sha1-96" => Ok(EncryptionType::AES256_CTS_HMAC_SHA1_96), + "aes128-cts-hmac-sha256-128" => Ok(EncryptionType::AES128_CTS_HMAC_SHA256_128), + "aes256-cts-hmac-sha384-192" => Ok(EncryptionType::AES256_CTS_HMAC_SHA384_192), + "rc4-hmac" => Ok(EncryptionType::RC4_HMAC), + "rc4-hmac-exp" => Ok(EncryptionType::RC4_HMAC_EXP), + "camellia128-cts-cmac" => Ok(EncryptionType::CAMELLIA128_CTS_CMAC), + "camellia256-cts-cmac" => Ok(EncryptionType::CAMELLIA256_CTS_CMAC), + "subkey-keymaterial" => Ok(EncryptionType::SUBKEY_KEYMATERIAL), + "rc4-md4" => Ok(EncryptionType::RC4_MD4), + "rc4-plain2" => Ok(EncryptionType::RC4_PLAIN2), + "rc4-lm" => Ok(EncryptionType::RC4_LM), + "rc4-sha" => Ok(EncryptionType::RC4_SHA), + "des-plain" => Ok(EncryptionType::DES_PLAIN), + "rc4-hmac-OLD" => Ok(EncryptionType::RC4_HMAC_OLD), + "rc4-plain-OLD" => Ok(EncryptionType::RC4_PLAIN_OLD), + "rc4-hmac-OLD-exp" => Ok(EncryptionType::RC4_HMAC_OLD_EXP), + "rc4-plain-OLD-exp" => Ok(EncryptionType::RC4_PLAIN_OLD_EXP), + "rc4-plain" => Ok(EncryptionType::RC4_PLAIN), + "rc4-plain-exp" => Ok(EncryptionType::RC4_PLAIN_EXP), + _ => { + if let Ok(num) = s.parse::<i32>() { + return Ok(EncryptionType(num)); + } else { + return Err(format!("'{}' is not a valid value for EncryptionType", s)); + } + } + } + } +} + +pub fn is_alphanumeric_or_dash(chr: char) -> bool { + return chr.is_alphanumeric() || chr == '-'; +} + +pub fn detect_parse_encryption_item(i: &str) -> IResult<&str, EncryptionType> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, e) = map_res(take_while1(is_alphanumeric_or_dash), |s: &str| { + EncryptionType::from_str(s) + })(i)?; + let (i, _) = opt(is_a(" "))(i)?; + let (i, _) = opt(char(','))(i)?; + return Ok((i, e)); +} + +pub fn detect_parse_encryption_list(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> { + let mut l = DetectKrb5TicketEncryptionList::new(); + let (i, v) = many1(detect_parse_encryption_item)(i)?; + for &val in v.iter() { + let vali = val.0; + if vali < 0 && ((-vali) as usize) < KRB_TICKET_FASTARRAY_SIZE { + l.negative[(-vali) as usize] = true; + } else if vali >= 0 && (vali as usize) < KRB_TICKET_FASTARRAY_SIZE { + l.positive[vali as usize] = true; + } else { + l.other.push(val); + } + } + return Ok((i, DetectKrb5TicketEncryptionData::LIST(l))); +} + +pub fn detect_parse_encryption(i: &str) -> IResult<&str, DetectKrb5TicketEncryptionData> { + let (i, _) = opt(is_a(" "))(i)?; + let (i, parsed) = alt((detect_parse_encryption_weak, detect_parse_encryption_list))(i)?; + let (i, _) = all_consuming(take_while(|c| c == ' '))(i)?; + return Ok((i, parsed)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_detect_encryption_parse( + ustr: *const std::os::raw::c_char, +) -> *mut DetectKrb5TicketEncryptionData { + let ft_name: &CStr = CStr::from_ptr(ustr); //unsafe + if let Ok(s) = ft_name.to_str() { + if let Ok((_, ctx)) = detect_parse_encryption(s) { + let boxed = Box::new(ctx); + return Box::into_raw(boxed) as *mut _; + } + } + return std::ptr::null_mut(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_detect_encryption_match( + tx: &mut KRB5Transaction, ctx: &DetectKrb5TicketEncryptionData, +) -> std::os::raw::c_int { + if let Some(x) = tx.ticket_etype { + match ctx { + DetectKrb5TicketEncryptionData::WEAK(w) => { + if (test_weak_encryption(x) && *w) || (!test_weak_encryption(x) && !*w) { + return 1; + } + } + DetectKrb5TicketEncryptionData::LIST(l) => { + let vali = x.0; + if vali < 0 && ((-vali) as usize) < KRB_TICKET_FASTARRAY_SIZE { + if l.negative[(-vali) as usize] { + return 1; + } + } else if vali >= 0 && (vali as usize) < KRB_TICKET_FASTARRAY_SIZE { + if l.positive[vali as usize] { + return 1; + } + } else { + for &val in l.other.iter() { + if x == val { + return 1; + } + } + } + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_detect_encryption_free(ctx: &mut DetectKrb5TicketEncryptionData) { + // Just unbox... + std::mem::drop(Box::from_raw(ctx)); +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_detect_parse_encryption() { + match detect_parse_encryption(" weak ") { + Ok((rem, ctx)) => { + match ctx { + DetectKrb5TicketEncryptionData::WEAK(w) => { + assert!(w); + } + _ => { + panic!("Result should have been weak."); + } + } + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + match detect_parse_encryption("!weak") { + Ok((rem, ctx)) => { + match ctx { + DetectKrb5TicketEncryptionData::WEAK(w) => { + assert!(!w); + } + _ => { + panic!("Result should have been weak."); + } + } + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + match detect_parse_encryption(" des-cbc-crc , -128,2 257") { + Ok((rem, ctx)) => { + match ctx { + DetectKrb5TicketEncryptionData::LIST(l) => { + assert!(l.positive[EncryptionType::DES_CBC_CRC.0 as usize]); + assert!(l.negative[128]); + assert!(l.positive[2]); + assert_eq!(l.other.len(), 1); + assert_eq!(l.other[0], EncryptionType(257)); + } + _ => { + panic!("Result should have been list."); + } + } + // And we should have no bytes left. + assert_eq!(rem.len(), 0); + } + _ => { + panic!("Result should have been ok."); + } + } + } +} diff --git a/rust/src/krb/krb5.rs b/rust/src/krb/krb5.rs new file mode 100644 index 0000000..3282d50 --- /dev/null +++ b/rust/src/krb/krb5.rs @@ -0,0 +1,638 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +use std; +use std::ffi::CString; +use nom7::{Err, IResult}; +use nom7::number::streaming::be_u32; +use der_parser::der::der_read_element_header; +use der_parser::ber::Class; +use kerberos_parser::krb5_parser; +use kerberos_parser::krb5::{EncryptionType,ErrorCode,MessageType,PrincipalName,Realm}; +use crate::applayer::{self, *}; +use crate::core; +use crate::core::{AppProto,Flow,ALPROTO_FAILED,ALPROTO_UNKNOWN,Direction}; + +#[derive(AppLayerEvent)] +pub enum KRB5Event { + MalformedData, + WeakEncryption, +} + +pub struct KRB5State { + state_data: AppLayerStateData, + + pub req_id: u8, + + pub record_ts: usize, + pub defrag_buf_ts: Vec<u8>, + pub record_tc: usize, + pub defrag_buf_tc: Vec<u8>, + + /// List of transactions for this session + transactions: Vec<KRB5Transaction>, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, +} + +impl State<KRB5Transaction> for KRB5State { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&KRB5Transaction> { + self.transactions.get(index) + } +} + +pub struct KRB5Transaction { + /// The message type: AS-REQ, AS-REP, etc. + pub msg_type: MessageType, + + /// The client PrincipalName, if present + pub cname: Option<PrincipalName>, + /// The server Realm, if present + pub realm: Option<Realm>, + /// The server PrincipalName, if present + pub sname: Option<PrincipalName>, + + /// Encryption used (only in AS-REP and TGS-REP) + pub etype: Option<EncryptionType>, + + /// Encryption used for ticket + pub ticket_etype: Option<EncryptionType>, + + /// Error code, if request has failed + pub error_code: Option<ErrorCode>, + + /// Message type of request. For using in responses. + pub req_type: Option<MessageType>, + + /// The internal transaction id + id: u64, + + tx_data: applayer::AppLayerTxData, +} + +impl Transaction for KRB5Transaction { + fn id(&self) -> u64 { + self.id + } +} + +impl Default for KRB5State { + fn default() -> Self { + Self::new() + } +} + +impl KRB5State { + pub fn new() -> KRB5State { + Self { + state_data: AppLayerStateData::new(), + req_id: 0, + record_ts: 0, + defrag_buf_ts: Vec::new(), + record_tc: 0, + defrag_buf_tc: Vec::new(), + transactions: Vec::new(), + tx_id: 0, + } + } + + /// Parse a Kerberos request message + /// + /// Returns 0 in case of success, or -1 on error + fn parse(&mut self, i: &[u8], direction: Direction) -> i32 { + match der_read_element_header(i) { + Ok((_rem,hdr)) => { + // Kerberos messages start with an APPLICATION header + if hdr.class() != Class::Application { return 0; } + match hdr.tag().0 { + 10 => { + let req = krb5_parser::parse_as_req(i); + if let Ok((_,kdc_req)) = req { + let mut tx = self.new_tx(direction); + tx.msg_type = MessageType::KRB_AS_REQ; + tx.cname = kdc_req.req_body.cname; + tx.realm = Some(kdc_req.req_body.realm); + tx.sname = kdc_req.req_body.sname; + tx.etype = None; + self.transactions.push(tx); + }; + self.req_id = 10; + }, + 11 => { + let res = krb5_parser::parse_as_rep(i); + if let Ok((_,kdc_rep)) = res { + let mut tx = self.new_tx(direction); + tx.msg_type = MessageType::KRB_AS_REP; + if self.req_id > 0 { + // set request type only if previous message + // was a request + tx.req_type = Some(MessageType(self.req_id.into())); + } + tx.cname = Some(kdc_rep.cname); + tx.realm = Some(kdc_rep.crealm); + tx.sname = Some(kdc_rep.ticket.sname); + tx.ticket_etype = Some(kdc_rep.ticket.enc_part.etype); + tx.etype = Some(kdc_rep.enc_part.etype); + self.transactions.push(tx); + if test_weak_encryption(kdc_rep.enc_part.etype) { + self.set_event(KRB5Event::WeakEncryption); + } + }; + self.req_id = 0; + }, + 12 => { + let req = krb5_parser::parse_tgs_req(i); + if let Ok((_,kdc_req)) = req { + let mut tx = self.new_tx(direction); + tx.msg_type = MessageType::KRB_TGS_REQ; + tx.cname = kdc_req.req_body.cname; + tx.realm = Some(kdc_req.req_body.realm); + tx.sname = kdc_req.req_body.sname; + tx.etype = None; + self.transactions.push(tx); + }; + self.req_id = 12; + }, + 13 => { + let res = krb5_parser::parse_tgs_rep(i); + if let Ok((_,kdc_rep)) = res { + let mut tx = self.new_tx(direction); + tx.msg_type = MessageType::KRB_TGS_REP; + if self.req_id > 0 { + // set request type only if previous message + // was a request + tx.req_type = Some(MessageType(self.req_id.into())); + } + tx.cname = Some(kdc_rep.cname); + tx.realm = Some(kdc_rep.crealm); + tx.ticket_etype = Some(kdc_rep.ticket.enc_part.etype); + tx.sname = Some(kdc_rep.ticket.sname); + tx.etype = Some(kdc_rep.enc_part.etype); + self.transactions.push(tx); + if test_weak_encryption(kdc_rep.enc_part.etype) { + self.set_event(KRB5Event::WeakEncryption); + } + }; + self.req_id = 0; + }, + 14 => { + self.req_id = 14; + }, + 15 => { + self.req_id = 0; + }, + 30 => { + let res = krb5_parser::parse_krb_error(i); + if let Ok((_,error)) = res { + let mut tx = self.new_tx(direction); + if self.req_id > 0 { + // set request type only if previous message + // was a request + tx.req_type = Some(MessageType(self.req_id.into())); + } + tx.msg_type = MessageType::KRB_ERROR; + tx.cname = error.cname; + tx.realm = error.crealm; + tx.sname = Some(error.sname); + tx.error_code = Some(error.error_code); + self.transactions.push(tx); + }; + self.req_id = 0; + }, + _ => { SCLogDebug!("unknown/unsupported tag {}", hdr.tag()); }, + } + 0 + }, + Err(Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing KRB5 data"); + self.set_event(KRB5Event::MalformedData); + -1 + }, + Err(_) => { + SCLogDebug!("Error while parsing KRB5 data"); + self.set_event(KRB5Event::MalformedData); + -1 + }, + } + } + + pub fn free(&mut self) { + // All transactions are freed when the `transactions` object is freed. + // But let's be explicit + self.transactions.clear(); + } + + fn new_tx(&mut self, direction: Direction) -> KRB5Transaction { + self.tx_id += 1; + KRB5Transaction::new(direction, self.tx_id) + } + + fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&KRB5Transaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + /// Set an event. The event is set on the most recent transaction. + fn set_event(&mut self, event: KRB5Event) { + if let Some(tx) = self.transactions.last_mut() { + tx.tx_data.set_event(event as u8); + } + } +} + +impl KRB5Transaction { + pub fn new(direction: Direction, id: u64) -> KRB5Transaction { + let krbtx = KRB5Transaction{ + msg_type: MessageType(0), + cname: None, + realm: None, + sname: None, + etype: None, + ticket_etype: None, + error_code: None, + req_type: None, + id, + tx_data: applayer::AppLayerTxData::for_direction(direction), + }; + return krbtx; + } +} + +/// Return true if Kerberos `EncryptionType` is weak +pub fn test_weak_encryption(alg:EncryptionType) -> bool { + match alg { + EncryptionType::AES128_CTS_HMAC_SHA1_96 | + EncryptionType::AES256_CTS_HMAC_SHA1_96 | + EncryptionType::AES128_CTS_HMAC_SHA256_128 | + EncryptionType::AES256_CTS_HMAC_SHA384_192 | + EncryptionType::CAMELLIA128_CTS_CMAC | + EncryptionType::CAMELLIA256_CTS_CMAC => false, + _ => true, // all other ciphers are weak or deprecated + } +} + + + + + +/// Returns *mut KRB5State +#[no_mangle] +pub extern "C" fn rs_krb5_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = KRB5State::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut KRB5State as void pointer +#[no_mangle] +pub extern "C" fn rs_krb5_state_free(state: *mut std::os::raw::c_void) { + let mut state: Box<KRB5State> = unsafe{Box::from_raw(state as _)}; + state.free(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_state_get_tx(state: *mut std::os::raw::c_void, + tx_id: u64) + -> *mut std::os::raw::c_void +{ + let state = cast_pointer!(state,KRB5State); + match state.get_tx_by_id(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_state_get_tx_count(state: *mut std::os::raw::c_void) + -> u64 +{ + let state = cast_pointer!(state,KRB5State); + state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_state_tx_free(state: *mut std::os::raw::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state,KRB5State); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_krb5_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, + _direction: u8) + -> std::os::raw::c_int +{ + 1 +} + +static mut ALPROTO_KRB5 : AppProto = ALPROTO_UNKNOWN; + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_probing_parser(_flow: *const Flow, + _direction: u8, + input:*const u8, input_len: u32, + _rdir: *mut u8) -> AppProto +{ + let slice = build_slice!(input,input_len as usize); + let alproto = ALPROTO_KRB5; + if slice.len() <= 10 { return ALPROTO_FAILED; } + match der_read_element_header(slice) { + Ok((rem, ref hdr)) => { + // Kerberos messages start with an APPLICATION header + if hdr.class() != Class::Application { return ALPROTO_FAILED; } + // Tag number should be <= 30 + if hdr.tag().0 > 30 { return ALPROTO_FAILED; } + // Kerberos messages contain sequences + if rem.is_empty() || rem[0] != 0x30 { return ALPROTO_FAILED; } + // Check kerberos version + if let Ok((rem,_hdr)) = der_read_element_header(rem) { + if rem.len() > 5 { + #[allow(clippy::single_match)] + match (rem[2],rem[3],rem[4]) { + // Encoding of DER integer 5 (version) + (2,1,5) => { return alproto; }, + _ => (), + } + } + } + return ALPROTO_FAILED; + }, + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + }, + Err(_) => { + return ALPROTO_FAILED; + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_probing_parser_tcp(_flow: *const Flow, + direction: u8, + input:*const u8, input_len: u32, + rdir: *mut u8) -> AppProto +{ + let slice = build_slice!(input,input_len as usize); + if slice.len() <= 14 { return ALPROTO_FAILED; } + match be_u32(slice) as IResult<&[u8],u32> { + Ok((rem, record_mark)) => { + // protocol implementations forbid very large requests + if record_mark > 16384 { return ALPROTO_FAILED; } + return rs_krb5_probing_parser(_flow, direction, + rem.as_ptr(), rem.len() as u32, rdir); + }, + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + }, + Err(_) => { + return ALPROTO_FAILED; + }, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_parse_request(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let buf = stream_slice.as_slice(); + let state = cast_pointer!(state,KRB5State); + if state.parse(buf, Direction::ToServer) < 0 { + return AppLayerResult::err(); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_parse_response(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let buf = stream_slice.as_slice(); + let state = cast_pointer!(state,KRB5State); + if state.parse(buf, Direction::ToClient) < 0 { + return AppLayerResult::err(); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_parse_request_tcp(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,KRB5State); + let buf = stream_slice.as_slice(); + + let mut v : Vec<u8>; + let tcp_buffer = match state.record_ts { + 0 => buf, + _ => { + // sanity check to avoid memory exhaustion + if state.defrag_buf_ts.len() + buf.len() > 100000 { + SCLogDebug!("rs_krb5_parse_request_tcp: TCP buffer exploded {} {}", + state.defrag_buf_ts.len(), buf.len()); + return AppLayerResult::err(); + } + v = state.defrag_buf_ts.split_off(0); + v.extend_from_slice(buf); + v.as_slice() + } + }; + let mut cur_i = tcp_buffer; + while !cur_i.is_empty() { + if state.record_ts == 0 { + match be_u32(cur_i) as IResult<&[u8],u32> { + Ok((rem,record)) => { + state.record_ts = record as usize; + cur_i = rem; + }, + Err(Err::Incomplete(_)) => { + state.defrag_buf_ts.extend_from_slice(cur_i); + return AppLayerResult::ok(); + } + _ => { + SCLogDebug!("rs_krb5_parse_request_tcp: reading record mark failed!"); + return AppLayerResult::err(); + } + } + } + if cur_i.len() >= state.record_ts { + if state.parse(cur_i, Direction::ToServer) < 0 { + return AppLayerResult::err(); + } + state.record_ts = 0; + cur_i = &cur_i[state.record_ts..]; + } else { + // more fragments required + state.defrag_buf_ts.extend_from_slice(cur_i); + return AppLayerResult::ok(); + } + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_krb5_parse_response_tcp(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,KRB5State); + let buf = stream_slice.as_slice(); + + let mut v : Vec<u8>; + let tcp_buffer = match state.record_tc { + 0 => buf, + _ => { + // sanity check to avoid memory exhaustion + if state.defrag_buf_tc.len() + buf.len() > 100000 { + SCLogDebug!("rs_krb5_parse_response_tcp: TCP buffer exploded {} {}", + state.defrag_buf_tc.len(), buf.len()); + return AppLayerResult::err(); + } + v = state.defrag_buf_tc.split_off(0); + v.extend_from_slice(buf); + v.as_slice() + } + }; + let mut cur_i = tcp_buffer; + while !cur_i.is_empty() { + if state.record_tc == 0 { + match be_u32(cur_i) as IResult<&[u8],_> { + Ok((rem,record)) => { + state.record_tc = record as usize; + cur_i = rem; + }, + Err(Err::Incomplete(_)) => { + state.defrag_buf_tc.extend_from_slice(cur_i); + return AppLayerResult::ok(); + } + _ => { + SCLogDebug!("reading record mark failed!"); + return AppLayerResult::ok(); + } + } + } + if cur_i.len() >= state.record_tc { + if state.parse(cur_i, Direction::ToClient) < 0 { + return AppLayerResult::err(); + } + state.record_tc = 0; + cur_i = &cur_i[state.record_tc..]; + } else { + // more fragments required + state.defrag_buf_tc.extend_from_slice(cur_i); + return AppLayerResult::ok(); + } + } + AppLayerResult::ok() +} + +export_tx_data_get!(rs_krb5_get_tx_data, KRB5Transaction); +export_state_data_get!(rs_krb5_get_state_data, KRB5State); + +const PARSER_NAME : &[u8] = b"krb5\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_register_krb5_parser() { + let default_port = CString::new("88").unwrap(); + let mut parser = RustParser { + name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port : default_port.as_ptr(), + ipproto : core::IPPROTO_UDP, + probe_ts : Some(rs_krb5_probing_parser), + probe_tc : Some(rs_krb5_probing_parser), + min_depth : 0, + max_depth : 16, + state_new : rs_krb5_state_new, + state_free : rs_krb5_state_free, + tx_free : rs_krb5_state_tx_free, + parse_ts : rs_krb5_parse_request, + parse_tc : rs_krb5_parse_response, + get_tx_count : rs_krb5_state_get_tx_count, + get_tx : rs_krb5_state_get_tx, + tx_comp_st_ts : 1, + tx_comp_st_tc : 1, + tx_get_progress : rs_krb5_tx_get_alstate_progress, + get_eventinfo : Some(KRB5Event::get_event_info), + get_eventinfo_byid : Some(KRB5Event::get_event_info_by_id), + localstorage_new : None, + localstorage_free : None, + get_tx_files : None, + get_tx_iterator : Some(applayer::state_get_tx_iterator::<KRB5State, KRB5Transaction>), + get_tx_data : rs_krb5_get_tx_data, + get_state_data : rs_krb5_get_state_data, + apply_tx_config : None, + flags : 0, + truncate : None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + // register UDP parser + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + // store the allocated ID for the probe function + ALPROTO_KRB5 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for KRB5/UDP."); + } + // register TCP parser + parser.ipproto = core::IPPROTO_TCP; + parser.probe_ts = Some(rs_krb5_probing_parser_tcp); + parser.probe_tc = Some(rs_krb5_probing_parser_tcp); + parser.parse_ts = rs_krb5_parse_request_tcp; + parser.parse_tc = rs_krb5_parse_response_tcp; + let ip_proto_str = CString::new("tcp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + // store the allocated ID for the probe function + ALPROTO_KRB5 = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for KRB5/TCP."); + } +} diff --git a/rust/src/krb/log.rs b/rust/src/krb/log.rs new file mode 100644 index 0000000..7cb9525 --- /dev/null +++ b/rust/src/krb/log.rs @@ -0,0 +1,74 @@ +/* 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 Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::krb::krb5::{KRB5Transaction,test_weak_encryption}; + +fn krb5_log_response(jsb: &mut JsonBuilder, tx: &mut KRB5Transaction) -> Result<(), JsonError> +{ + match tx.error_code { + Some(c) => { + jsb.set_string("msg_type", &format!("{:?}", tx.msg_type))?; + if let Some(req_type) = tx.req_type { + jsb.set_string("failed_request", &format!("{:?}", req_type))?; + } else { + // In case we capture the response but not the request + // we can't know the failed request type, since it could be + // AS-REQ or TGS-REQ + jsb.set_string("failed_request", "UNKNOWN")?; + } + jsb.set_string("error_code", &format!("{:?}", c))?; + }, + None => { jsb.set_string("msg_type", &format!("{:?}", tx.msg_type))?; }, + } + let cname = match tx.cname { + Some(ref x) => format!("{}", x), + None => "<empty>".to_owned(), + }; + let realm = match tx.realm { + Some(ref x) => x.0.to_string(), + None => "<empty>".to_owned(), + }; + let sname = match tx.sname { + Some(ref x) => format!("{}", x), + None => "<empty>".to_owned(), + }; + let encryption = match tx.etype { + Some(ref x) => format!("{:?}", x), + None => "<none>".to_owned(), + }; + jsb.set_string("cname", &cname)?; + jsb.set_string("realm", &realm)?; + jsb.set_string("sname", &sname)?; + jsb.set_string("encryption", &encryption)?; + jsb.set_bool("weak_encryption", tx.etype.map_or(false,test_weak_encryption))?; + if let Some(x) = tx.ticket_etype { + let refs = format!("{:?}", x); + jsb.set_string("ticket_encryption", &refs)?; + jsb.set_bool("ticket_weak_encryption", test_weak_encryption(x))?; + } + + return Ok(()); +} + +#[no_mangle] +pub extern "C" fn rs_krb5_log_json_response(jsb: &mut JsonBuilder, tx: &mut KRB5Transaction) -> bool +{ + krb5_log_response(jsb, tx).is_ok() +} diff --git a/rust/src/krb/mod.rs b/rust/src/krb/mod.rs new file mode 100644 index 0000000..ca6237d --- /dev/null +++ b/rust/src/krb/mod.rs @@ -0,0 +1,24 @@ +/* Copyright (C) 2017-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. + */ + +//! Kerberos-v5 application layer, logger and detection module. + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +pub mod krb5; +pub mod detect; +pub mod log; diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..15e21c4 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,122 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Suricata is a network intrusion prevention and monitoring engine. +//! +//! Suricata is a hybrid C and Rust application. What is found here are +//! the components written in Rust. + +#![cfg_attr(feature = "strict", deny(warnings))] + +// Allow these patterns as its a style we like. +#![allow(clippy::needless_return)] +#![allow(clippy::let_and_return)] +#![allow(clippy::uninlined_format_args)] + +// We find this is beyond what the linter should flag. +#![allow(clippy::items_after_test_module)] + +// We find this makes sense at time. +#![allow(clippy::module_inception)] + +// The match macro is not always more clear. But its use is +// recommended where it makes sense. +#![allow(clippy::match_like_matches_macro)] + +// Something we should be conscious of, but due to interfacing with C +// is unavoidable at this time. +#![allow(clippy::too_many_arguments)] + +// This would be nice, but having this lint enables causes +// clippy --fix to make changes that don't meet our MSRV. +#![allow(clippy::derivable_impls)] + +// TODO: All unsafe functions should have a safety doc, even if its +// just due to FFI. +#![allow(clippy::missing_safety_doc)] + +#[macro_use] +extern crate bitflags; +extern crate byteorder; +extern crate crc; +extern crate memchr; +#[macro_use] +extern crate num_derive; +extern crate widestring; + +extern crate der_parser; +extern crate kerberos_parser; +extern crate tls_parser; +extern crate x509_parser; + +#[macro_use] +extern crate suricata_derive; + +#[macro_use] +pub mod log; + +#[macro_use] +pub mod core; + +#[macro_use] +pub mod common; +pub mod conf; +pub mod jsonbuilder; +#[macro_use] +pub mod applayer; +pub mod frames; +pub mod filecontainer; +pub mod filetracker; +pub mod kerberos; +pub mod detect; + +#[cfg(feature = "lua")] +pub mod lua; + +pub mod dns; +pub mod nfs; +pub mod ftp; +pub mod smb; +pub mod krb; +pub mod dcerpc; +pub mod modbus; + +pub mod ike; +pub mod snmp; + +pub mod ntp; +pub mod tftp; +pub mod dhcp; +pub mod sip; +pub mod rfb; +pub mod mqtt; +pub mod pgsql; +pub mod telnet; +pub mod applayertemplate; +pub mod rdp; +pub mod x509; +pub mod asn1; +pub mod mime; +pub mod ssh; +pub mod http2; +pub mod quic; +pub mod bittorrent_dht; +pub mod plugin; +pub mod lzma; +pub mod util; +pub mod ffi; +pub mod feature; diff --git a/rust/src/log.rs b/rust/src/log.rs new file mode 100644 index 0000000..7bf0be8 --- /dev/null +++ b/rust/src/log.rs @@ -0,0 +1,219 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Logging utility module. + +use std; +use std::ffi::CString; +use std::path::Path; + +use crate::core::*; + +#[derive(Debug)] +#[repr(C)] +pub enum Level { + NotSet = -1, + _None = 0, + Error, + Warning, + Notice, + Info, + _Perf, + Config, + #[cfg(feature = "debug")] + Debug, +} + +pub static mut LEVEL: i32 = Level::NotSet as i32; + +pub fn get_log_level() -> i32 { + unsafe { + LEVEL + } +} + +pub fn log_set_level(level: i32) { + unsafe { + LEVEL = level; + } +} + +#[no_mangle] +pub extern "C" fn rs_log_set_level(level: i32) { + log_set_level(level); +} + +fn basename(filename: &str) -> &str { + let path = Path::new(filename); + if let Some(os_str) = path.file_name() { + if let Some(basename) = os_str.to_str() { + return basename; + } + } + return filename; +} + +pub fn sclog(level: Level, file: &str, line: u32, function: &str, + message: &str) +{ + let filename = basename(file); + let noext = &filename[0..filename.len() - 3]; + sc_log_message(level, + filename, + line, + function, + noext, + message); +} + +// This macro returns the function name. +// +// This macro has been borrowed from https://github.com/popzxc/stdext-rs, which +// is released under the MIT license as there is currently no macro in Rust +// to provide the function name. +#[macro_export(local_inner_macros)] +macro_rules!function { + () => {{ + // Okay, this is ugly, I get it. However, this is the best we can get on a stable rust. + fn __f() {} + fn type_name_of<T>(_: T) -> &'static str { + std::any::type_name::<T>() + } + let name = type_name_of(__f); + &name[..name.len() - 5] + }} +} + +#[macro_export] +macro_rules!do_log { + ($level:expr, $($arg:tt)*) => { + if $crate::log::get_log_level() >= $level as i32 { + $crate::log::sclog($level, file!(), line!(), $crate::function!(), + &(format!($($arg)*))); + } + } +} + +#[macro_export] +macro_rules!SCLogError { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Error, $($arg)*); + }; +} + +#[macro_export] +macro_rules!SCLogWarning { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Warning, $($arg)*); + }; +} + +#[macro_export] +macro_rules!SCLogNotice { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Notice, $($arg)*); + } +} + +#[macro_export] +macro_rules!SCLogInfo { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Info, $($arg)*); + } +} + +#[macro_export] +macro_rules!SCLogPerf { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Perf, $($arg)*); + } +} + +#[macro_export] +macro_rules!SCLogConfig { + ($($arg:tt)*) => { + $crate::do_log!($crate::log::Level::Config, $($arg)*); + } +} + +// Debug mode: call C SCLogDebug +#[cfg(feature = "debug")] +#[macro_export] +macro_rules!SCLogDebug { + ($($arg:tt)*) => { + do_log!($crate::log::Level::Debug, $($arg)*); + } +} + +// SCLogDebug variation to use when not compiled with debug support. +// +// This macro will only use the parameters passed to prevent warnings +// about unused variables, but is otherwise the equivalent to a no-op. +#[cfg(not(feature = "debug"))] +#[macro_export] +macro_rules!SCLogDebug { + ($($arg:tt)*) => () +} + +/// SCLogMessage wrapper. If the Suricata C context is not registered +/// a more basic log format will be used (for example, when running +/// Rust unit tests). +pub fn sc_log_message(level: Level, + filename: &str, + line: std::os::raw::c_uint, + function: &str, + module: &str, + message: &str) -> std::os::raw::c_int +{ + unsafe { + if let Some(c) = SC { + return (c.SCLogMessage)( + level as i32, + to_safe_cstring(filename).as_ptr(), + line, + to_safe_cstring(function).as_ptr(), + to_safe_cstring(module).as_ptr(), + to_safe_cstring(message).as_ptr()); + } + } + + // Fall back if the Suricata C context is not registered which is + // the case when Rust unit tests are running. + // + // We don't log the time right now as I don't think it can be done + // with Rust 1.7.0 without using an external crate. With Rust + // 1.8.0 and newer we can unix UNIX_EPOCH.elapsed() to get the + // unix time. + println!("{}:{} <{:?}> -- {}", filename, line, level, message); + return 0; +} + +// Convert a &str into a CString by first stripping NUL bytes. +fn to_safe_cstring(val: &str) -> CString { + let mut safe = Vec::with_capacity(val.len()); + for c in val.as_bytes() { + if *c != 0 { + safe.push(*c); + } + } + match CString::new(safe) { + Ok(cstr) => cstr, + _ => { + CString::new("<failed to encode string>").unwrap() + } + } +} diff --git a/rust/src/lua.rs b/rust/src/lua.rs new file mode 100644 index 0000000..4fce2a8 --- /dev/null +++ b/rust/src/lua.rs @@ -0,0 +1,69 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Lua wrapper module. + +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::os::raw::c_long; + +#[cfg(feature = "lua_int8")] +type LuaInteger = i64; + +#[cfg(not(feature = "lua_int8"))] +type LuaInteger = i32; + +/// The Rust place holder for lua_State. +pub enum CLuaState {} + +extern { + fn lua_createtable(lua: *mut CLuaState, narr: c_int, nrec: c_int); + fn lua_settable(lua: *mut CLuaState, idx: c_long); + fn lua_pushlstring(lua: *mut CLuaState, s: *const c_char, len: usize); + fn lua_pushinteger(lua: *mut CLuaState, n: LuaInteger); +} + +pub struct LuaState { + pub lua: *mut CLuaState, +} + +impl LuaState { + + pub fn newtable(&self) { + unsafe { + lua_createtable(self.lua, 0, 0); + } + } + + pub fn settable(&self, idx: i64) { + unsafe { + lua_settable(self.lua, idx as c_long); + } + } + + pub fn pushstring(&self, val: &str) { + unsafe { + lua_pushlstring(self.lua, val.as_ptr() as *const c_char, val.len()); + } + } + + pub fn pushinteger(&self, val: i64) { + unsafe { + lua_pushinteger(self.lua, val as LuaInteger); + } + } +} diff --git a/rust/src/lzma.rs b/rust/src/lzma.rs new file mode 100644 index 0000000..e10d803 --- /dev/null +++ b/rust/src/lzma.rs @@ -0,0 +1,87 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! lzma decompression utility module. + +use lzma_rs::decompress::{Options, Stream}; +use lzma_rs::error::Error; +use std::io::{Cursor, Write}; + +/// Propagate lzma crate errors +#[repr(C)] +pub enum LzmaStatus { + LzmaOk, + LzmaIoError, + LzmaHeaderTooShortError, + LzmaError, + LzmaMemoryError, + LzmaXzError, +} + +impl From<Error> for LzmaStatus { + fn from(e: Error) -> LzmaStatus { + match e { + Error::IoError(_) => LzmaStatus::LzmaIoError, + Error::HeaderTooShort(_) => LzmaStatus::LzmaHeaderTooShortError, + Error::LzmaError(e) => { + if e.contains("exceeded memory limit") { + LzmaStatus::LzmaMemoryError + } else { + LzmaStatus::LzmaError + } + } + Error::XzError(_) => LzmaStatus::LzmaXzError, + } + } +} + +impl From<std::io::Error> for LzmaStatus { + fn from(_e: std::io::Error) -> LzmaStatus { + LzmaStatus::LzmaIoError + } +} + +/// Use the lzma algorithm to decompress a chunk of data. +#[no_mangle] +pub unsafe extern "C" fn lzma_decompress( + input: *const u8, input_len: &mut usize, output: *mut u8, output_len: &mut usize, + memlimit: usize, +) -> LzmaStatus { + let input = std::slice::from_raw_parts(input, *input_len); + let output = std::slice::from_raw_parts_mut(output, *output_len); + let output = Cursor::new(output); + + let options = Options { + memlimit: Some(memlimit), + allow_incomplete: true, + ..Default::default() + }; + + let mut stream = Stream::new_with_options(&options, output); + + if let Err(e) = stream.write_all(input) { + return e.into(); + } + + match stream.finish() { + Ok(output) => { + *output_len = output.position() as usize; + LzmaStatus::LzmaOk + } + Err(e) => e.into(), + } +} diff --git a/rust/src/mime/mod.rs b/rust/src/mime/mod.rs new file mode 100644 index 0000000..6f4a9bc --- /dev/null +++ b/rust/src/mime/mod.rs @@ -0,0 +1,229 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! MIME protocol parser module. + +use crate::common::nom7::take_until_and_consume; +use nom7::branch::alt; +use nom7::bytes::streaming::{tag, take_until, take_while}; +use nom7::combinator::{complete, opt, rest}; +use nom7::error::{make_error, ErrorKind}; +use nom7::{Err, IResult}; +use std; +use std::collections::HashMap; + +#[derive(Clone)] +pub struct MIMEHeaderTokens<'a> { + pub tokens: HashMap<&'a [u8], &'a [u8]>, +} + +pub fn mime_parse_value_delimited(input: &[u8]) -> IResult<&[u8], &[u8]> { + let (input, _) = tag("\"")(input)?; + let (input, value) = take_until("\"")(input)?; + let (input, _) = tag("\"")(input)?; + return Ok((input, value)); +} + +pub fn mime_parse_header_token(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> { + // from RFC2047 : like ch.is_ascii_whitespace but without 0x0c FORM-FEED + let (input, _) = take_while(|ch: u8| ch == 0x20 + || ch == 0x09 + || ch == 0x0a + || ch == 0x0d)(input)?; + let (input, name) = take_until("=")(input)?; + let (input, _) = tag("=")(input)?; + let (input, value) = alt(( + mime_parse_value_delimited, + complete(take_until(";")), + rest + ))(input)?; + let (input, _) = opt(complete(tag(";")))(input)?; + return Ok((input, (name, value))); +} + +fn mime_parse_header_tokens(input: &[u8]) -> IResult<&[u8], MIMEHeaderTokens> { + let (mut input, _) = take_until_and_consume(b";")(input)?; + let mut tokens = HashMap::new(); + while !input.is_empty() { + match mime_parse_header_token(input) { + Ok((rem, t)) => { + tokens.insert(t.0, t.1); + // should never happen + debug_validate_bug_on!(input.len() == rem.len()); + if input.len() == rem.len() { + //infinite loop + return Err(Err::Error(make_error(input, ErrorKind::Eof))); + } + input = rem; + } + Err(_) => { + // keep first tokens is error in remaining buffer + break; + } + } + } + return Ok((input, MIMEHeaderTokens { tokens })); +} + +fn mime_find_header_token<'a>( + header: &'a [u8], token: &[u8], sections_values: &'a mut Vec<u8>, +) -> Result<&'a [u8], ()> { + match mime_parse_header_tokens(header) { + Ok((_rem, t)) => { + // in case of multiple sections for the parameter cf RFC2231 + let mut current_section_slice = Vec::new(); + + // look for the specific token + match t.tokens.get(token) { + // easy nominal case + Some(value) => return Ok(value), + None => { + // check for initial section of a parameter + current_section_slice.extend_from_slice(token); + current_section_slice.extend_from_slice(b"*0"); + match t.tokens.get(¤t_section_slice[..]) { + Some(value) => { + sections_values.extend_from_slice(value); + let l = current_section_slice.len(); + current_section_slice[l - 1] = b'1'; + } + None => return Err(()), + } + } + } + + let mut current_section_seen = 1; + // we have at least the initial section + // try looping until we do not find anymore a next section + loop { + match t.tokens.get(¤t_section_slice[..]) { + Some(value) => { + sections_values.extend_from_slice(value); + current_section_seen += 1; + let nbdigits = current_section_slice.len() - token.len() - 1; + current_section_slice.truncate(current_section_slice.len() - nbdigits); + current_section_slice + .extend_from_slice(current_section_seen.to_string().as_bytes()); + } + None => return Ok(sections_values), + } + } + } + Err(_) => { + return Err(()); + } + } +} + +// used on the C side +pub const RS_MIME_MAX_TOKEN_LEN: usize = 255; + +#[no_mangle] +pub unsafe extern "C" fn rs_mime_find_header_token( + hinput: *const u8, hlen: u32, tinput: *const u8, tlen: u32, outbuf: &mut [u8; 255], + outlen: *mut u32, +) -> bool { + let hbuf = build_slice!(hinput, hlen as usize); + let tbuf = build_slice!(tinput, tlen as usize); + let mut sections_values = Vec::new(); + if let Ok(value) = mime_find_header_token(hbuf, tbuf, &mut sections_values) { + // limit the copy to the supplied buffer size + if value.len() <= RS_MIME_MAX_TOKEN_LEN { + outbuf[..value.len()].clone_from_slice(value); + } else { + outbuf.clone_from_slice(&value[..RS_MIME_MAX_TOKEN_LEN]); + } + *outlen = value.len() as u32; + return true; + } + return false; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_mime_find_header_token() { + let mut outvec = Vec::new(); + let undelimok = mime_find_header_token( + "attachment; filename=test;".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(undelimok, Ok("test".as_bytes())); + + let delimok = mime_find_header_token( + "attachment; filename=\"test2\";".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(delimok, Ok("test2".as_bytes())); + + let evasion_othertoken = mime_find_header_token( + "attachment; dummy=\"filename=wrong\"; filename=real;".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(evasion_othertoken, Ok("real".as_bytes())); + + let evasion_suffixtoken = mime_find_header_token( + "attachment; notafilename=wrong; filename=good;".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(evasion_suffixtoken, Ok("good".as_bytes())); + + let badending = mime_find_header_token( + "attachment; filename=oksofar; badending".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(badending, Ok("oksofar".as_bytes())); + + let missend = mime_find_header_token( + "attachment; filename=test".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(missend, Ok("test".as_bytes())); + + let spaces = mime_find_header_token( + "attachment; filename=test me wrong".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(spaces, Ok("test me wrong".as_bytes())); + + assert_eq!(outvec.len(), 0); + let multi = mime_find_header_token( + "attachment; filename*0=abc; filename*1=\"def\";".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(multi, Ok("abcdef".as_bytes())); + outvec.clear(); + + let multi = mime_find_header_token( + "attachment; filename*1=456; filename*0=\"123\"".as_bytes(), + "filename".as_bytes(), + &mut outvec, + ); + assert_eq!(multi, Ok("123456".as_bytes())); + outvec.clear(); + } +} diff --git a/rust/src/modbus/detect.rs b/rust/src/modbus/detect.rs new file mode 100644 index 0000000..fdd5d51 --- /dev/null +++ b/rust/src/modbus/detect.rs @@ -0,0 +1,1451 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::modbus::ModbusTransaction; +use crate::debug_validate_bug_on; +use lazy_static::lazy_static; +use regex::Regex; +use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message}; +use std::ffi::CStr; +use std::ops::{Range, RangeInclusive}; +use std::os::raw::{c_char, c_void}; +use std::str::FromStr; + +lazy_static! { + static ref ACCESS_RE: Regex = Regex::new( + "^[[:space:]]*\"?[[:space:]]*access[[:space:]]*(read|write)\ + [[:space:]]*(discretes|coils|input|holding)?\ + (?:,[[:space:]]*address[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?\ + (?:,[[:space:]]*value[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?)?)?\ + [[:space:]]*\"?[[:space:]]*$" + ) + .unwrap(); + static ref FUNC_RE: Regex = Regex::new( + "^[[:space:]]*\"?[[:space:]]*function[[:space:]]*(!?[A-z0-9]+)\ + (?:,[[:space:]]*subfunction[[:space:]]+([[:digit:]]+))?[[:space:]]*\"?[[:space:]]*$" + ) + .unwrap(); + static ref UNIT_RE: Regex = Regex::new( + "^[[:space:]]*\"?[[:space:]]*unit[[:space:]]+([<>]?[[:digit:]]+)\ + (?:<>([[:digit:]]+))?(?:,[[:space:]]*(.*))?[[:space:]]*\"?[[:space:]]*$" + ) + .unwrap(); +} + +#[derive(Debug, PartialEq, Default)] +pub struct DetectModbusRust { + category: Option<Flags<CodeCategory>>, + function: Option<FunctionCode>, + subfunction: Option<u16>, + access_type: Option<Flags<AccessType>>, + unit_id: Option<Range<u16>>, + address: Option<Range<u16>>, + value: Option<Range<u16>>, +} + +/// Compares a range from the alert signature to the transaction's unit_id/address/value +/// range. If the signature's range intersects with the transaction, it is a match and true is +/// returned. +fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool { + if sig_range.start == sig_range.end { + sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end() + } else if sig_range.start == std::u16::MIN { + sig_range.end > *trans_range.start() + } else if sig_range.end == std::u16::MAX { + sig_range.start < *trans_range.end() + } else { + sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end + } +} + +/// Compares a range from the alert signature to the transaction's unit_id/address/value. +/// If the signature's range intersects with the transaction, it is a match and true is +/// returned. +fn check_match(sig_range: &Range<u16>, value: u16) -> bool { + if sig_range.start == sig_range.end { + sig_range.start == value + } else if sig_range.start == std::u16::MIN { + sig_range.end > value + } else if sig_range.end == std::u16::MAX { + sig_range.start < value + } else { + sig_range.start < value && value < sig_range.end + } +} + +/// Gets the min/max range of an alert signature from the respective capture groups. +/// In the case where the max is not given, it is set based on the first char of the min str +/// which indicates what range we are looking for: +/// '<' = std::u16::MIN..min +/// '>' = min..std::u16::MAX +/// _ = min..min +/// If the max is given, the range returned is min..max +fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> { + if max_str.is_empty() { + if let Some(sign) = min_str.chars().next() { + debug_validate_bug_on!(!sign.is_ascii_digit() && sign != '<' && sign != '>'); + match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() { + Ok(num) => match sign { + '>' => Ok(num..std::u16::MAX), + '<' => Ok(std::u16::MIN..num), + _ => Ok(num..num), + }, + Err(_) => { + SCLogError!("Invalid min number: {}", min_str); + Err(()) + } + } + } else { + Err(()) + } + } else { + let min = match min_str.parse::<u16>() { + Ok(num) => num, + Err(_) => { + SCLogError!("Invalid min number: {}", min_str); + return Err(()); + } + }; + + let max = match max_str.parse::<u16>() { + Ok(num) => num, + Err(_) => { + SCLogError!("Invalid max number: {}", max_str); + return Err(()); + } + }; + + Ok(min..max) + } +} + +/// Intermediary function between the C code and the parsing functions. +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void { + if c_arg.is_null() { + return std::ptr::null_mut(); + } + if let Ok(arg) = CStr::from_ptr(c_arg).to_str() { + match parse_unit_id(arg) + .or_else(|_| parse_function(arg)) + .or_else(|_| parse_access(arg)) + { + Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void, + Err(()) => return std::ptr::null_mut(), + } + } + std::ptr::null_mut() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) { + if !ptr.is_null() { + let _ = Box::from_raw(ptr as *mut DetectModbusRust); + } +} + +/// Compares a transaction to a signature to determine whether the transaction +/// matches the signature. If it does, 1 is returned; otherwise 0 is returned. +#[no_mangle] +pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 { + // All necessary information can be found in the request (value inspection currently + // only supports write functions, which hold the value in the request). + // Only inspect the response in the case where there is no request. + let msg = match &tx.request { + Some(r) => r, + None => match &tx.response { + Some(r) => r, + None => return 0, + }, + }; + + if let Some(unit_id) = &modbus.unit_id { + if !check_match(unit_id, msg.unit_id.into()) { + return 0; + } + } + + if let Some(access_type) = &modbus.access_type { + let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE); + let access_func = *access_type & AccessType::FUNC_MASK; + + if rd_wr_access.is_empty() + || !msg.access_type.intersects(rd_wr_access) + || (!access_func.is_empty() && !msg.access_type.intersects(access_func)) + { + return 0; + } + + return inspect_data(msg, modbus) as u8; + } + + if let Some(category) = modbus.category { + return u8::from(msg.category.intersects(category)); + } + + match &modbus.function { + Some(func) if func == &msg.function.code => match modbus.subfunction { + Some(subfunc) => { + if let Data::Diagnostic { func, data: _ } = &msg.data { + u8::from(subfunc == func.raw) + } else { + 0 + } + } + None => 1, + }, + None => 1, + _ => 0, + } +} + +/// Compares the transaction's data with the signature to determine whether or +/// not it is a match +fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool { + let sig_address = if let Some(sig_addr) = &modbus.address { + // Compare the transaction's address with the signature to determine whether or + // not it is a match + if let Some(req_addr) = msg.get_address_range() { + if !check_match_range(sig_addr, req_addr) { + return false; + } + } else { + return false; + } + + sig_addr.start + } else { + return true; + }; + + let sig_value = if let Some(value) = &modbus.value { + value + } else { + return true; + }; + + if let Some(value) = msg.get_write_value_at_address(sig_address) { + check_match(sig_value, value) + } else { + false + } +} + +/// Parses the access type for the signature +fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> { + let re = if let Some(re) = ACCESS_RE.captures(access_str) { + re + } else { + return Err(()); + }; + + // 1: Read | Write + let mut access_type: Flags<AccessType> = match re.get(1) { + Some(access) => match AccessType::from_str(access.as_str()) { + Ok(access_type) => access_type.into(), + Err(_) => { + SCLogError!("Unknown access keyword {}", access.as_str()); + return Err(()); + } + }, + None => { + SCLogError!("No access keyword found"); + return Err(()); + } + }; + + // 2: Discretes | Coils | Input | Holding + access_type = match re.get(2) { + Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS, + Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING, + Some(x) if x.as_str() == "discretes" => { + if access_type == AccessType::WRITE { + SCLogError!("Discrete access is only read access"); + return Err(()); + } + access_type | AccessType::DISCRETES + } + Some(x) if x.as_str() == "input" => { + if access_type == AccessType::WRITE { + SCLogError!("Input access is only read access"); + return Err(()); + } + access_type | AccessType::INPUT + } + Some(unknown) => { + SCLogError!("Unknown access keyword {}", unknown.as_str()); + return Err(()); + } + None => access_type, + }; + + // 3: Address min + let address = if let Some(min) = re.get(3) { + // 4: Address max + let max_str = if let Some(max) = re.get(4) { + max.as_str() + } else { + "" + }; + parse_range(min.as_str(), max_str)? + } else { + return Ok(DetectModbusRust { + access_type: Some(access_type), + ..Default::default() + }); + }; + + // 5: Value min + let value = if let Some(min) = re.get(5) { + if address.start != address.end { + SCLogError!("rule contains conflicting keywords (address range and value)."); + return Err(()); + } + + if access_type == AccessType::READ { + SCLogError!("Value keyword only works in write access"); + return Err(()); + } + + // 6: Value max + let max_str = if let Some(max) = re.get(6) { + max.as_str() + } else { + "" + }; + + parse_range(min.as_str(), max_str)? + } else { + return Ok(DetectModbusRust { + access_type: Some(access_type), + address: Some(address), + ..Default::default() + }); + }; + + Ok(DetectModbusRust { + access_type: Some(access_type), + address: Some(address), + value: Some(value), + ..Default::default() + }) +} + +fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> { + let re = if let Some(re) = FUNC_RE.captures(func_str) { + re + } else { + return Err(()); + }; + + let mut modbus: DetectModbusRust = Default::default(); + + // 1: Function + if let Some(x) = re.get(1) { + let word = x.as_str(); + + // Digit + if let Ok(num) = word.parse::<u8>() { + if num == 0 { + SCLogError!("Invalid modbus function value"); + return Err(()); + } + + modbus.function = Some(FunctionCode::from_raw(num)); + + // 2: Subfunction (optional) + match re.get(2) { + Some(x) => { + let subfunc = x.as_str(); + match subfunc.parse::<u16>() { + Ok(num) => { + modbus.subfunction = Some(num); + } + Err(_) => { + SCLogError!("Invalid subfunction value: {}", subfunc); + return Err(()); + } + } + } + None => return Ok(modbus), + } + } + // Non-digit + else { + let neg = word.starts_with('!'); + + let category = match &word[neg as usize..] { + "assigned" => CodeCategory::PUBLIC_ASSIGNED.into(), + "unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(), + "public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED, + "user" => CodeCategory::USER_DEFINED.into(), + "reserved" => CodeCategory::RESERVED.into(), + "all" => { + CodeCategory::PUBLIC_ASSIGNED + | CodeCategory::PUBLIC_UNASSIGNED + | CodeCategory::USER_DEFINED + | CodeCategory::RESERVED + } + _ => { + SCLogError!("Keyword unknown: {}", word); + return Err(()); + } + }; + + if neg { + modbus.category = Some(!category); + } else { + modbus.category = Some(category); + } + } + } else { + return Err(()); + } + + Ok(modbus) +} + +fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> { + let re = if let Some(re) = UNIT_RE.captures(unit_str) { + re + } else { + return Err(()); + }; + + // 3: Either function or access string + let mut modbus = if let Some(x) = re.get(3) { + let extra = x.as_str(); + if let Ok(mbus) = parse_function(extra) { + mbus + } else if let Ok(mbus) = parse_access(extra) { + mbus + } else { + SCLogError!("Invalid modbus option: {}", extra); + return Err(()); + } + } else { + Default::default() + }; + + // 1: Unit ID min + if let Some(min) = re.get(1) { + // 2: Unit ID max + let max_str = if let Some(max) = re.get(2) { + max.as_str() + } else { + "" + }; + + modbus.unit_id = Some(parse_range(min.as_str(), max_str)?); + } else { + SCLogError!("Min modbus unit ID not found"); + return Err(()); + } + + Ok(modbus) +} + +#[cfg(test)] +mod test { + use super::super::modbus::ModbusState; + use super::*; + use crate::applayer::*; + use sawp::parser::Direction; + + #[test] + fn test_parse() { + assert_eq!( + parse_function("function 1"), + Ok(DetectModbusRust { + function: Some(FunctionCode::RdCoils), + ..Default::default() + }) + ); + assert_eq!( + parse_function("function 8, subfunction 4"), + Ok(DetectModbusRust { + function: Some(FunctionCode::Diagnostic), + subfunction: Some(4), + ..Default::default() + }) + ); + assert_eq!( + parse_function("function reserved"), + Ok(DetectModbusRust { + category: Some(Flags::from(CodeCategory::RESERVED)), + ..Default::default() + }) + ); + assert_eq!( + parse_function("function !assigned"), + Ok(DetectModbusRust { + category: Some(!CodeCategory::PUBLIC_ASSIGNED), + ..Default::default() + }) + ); + + assert_eq!( + parse_access("access read"), + Ok(DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access read discretes"), + Ok(DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::DISCRETES), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access read, address 1000"), + Ok(DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + address: Some(1000..1000), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access write coils, address <500"), + Ok(DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::COILS), + address: Some(std::u16::MIN..500), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access write coils, address >500"), + Ok(DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::COILS), + address: Some(500..std::u16::MAX), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access write holding, address 100, value <1000"), + Ok(DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(100..100), + value: Some(std::u16::MIN..1000), + ..Default::default() + }) + ); + assert_eq!( + parse_access("access write holding, address 100, value 500<>1000"), + Ok(DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(100..100), + value: Some(500..1000), + ..Default::default() + }) + ); + + assert_eq!( + parse_unit_id("unit 10"), + Ok(DetectModbusRust { + unit_id: Some(10..10), + ..Default::default() + }) + ); + assert_eq!( + parse_unit_id("unit 10, function 8, subfunction 4"), + Ok(DetectModbusRust { + function: Some(FunctionCode::Diagnostic), + subfunction: Some(4), + unit_id: Some(10..10), + ..Default::default() + }) + ); + assert_eq!( + parse_unit_id("unit 10, access read, address 1000"), + Ok(DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + unit_id: Some(10..10), + address: Some(1000..1000), + ..Default::default() + }) + ); + assert_eq!( + parse_unit_id("unit <11"), + Ok(DetectModbusRust { + unit_id: Some(std::u16::MIN..11), + ..Default::default() + }) + ); + assert_eq!( + parse_unit_id("unit 10<>500"), + Ok(DetectModbusRust { + unit_id: Some(10..500), + ..Default::default() + }) + ); + + assert_eq!(parse_unit_id("unit ๖"), Err(())); + + assert_eq!(parse_access("access write holdin"), Err(())); + assert_eq!(parse_access("unt 10"), Err(())); + assert_eq!( + parse_access("access write holding, address 100, value 500<>"), + Err(()) + ); + } + + #[test] + fn test_match() { + let mut modbus = ModbusState::new(); + + // Read/Write Multiple Registers Request + assert_eq!( + modbus.parse( + &[ + 0x12, 0x34, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x11, // Length + 0x0a, // Unit ID + 0x17, // Function code + 0x00, 0x03, // Read Starting Address + 0x00, 0x06, // Quantity to Read + 0x00, 0x0E, // Write Starting Address + 0x00, 0x03, // Quantity to Write + 0x06, // Write Byte count + 0x12, 0x34, // Write Registers Value + 0x56, 0x78, 0x9A, 0xBC + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 1); + // function 23 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + function: Some(FunctionCode::RdWrMultRegs), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 15, value <4660 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(15..15), + value: Some(std::u16::MIN..4660), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 15, value 4661 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(15..15), + value: Some(4661..4661), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 16, value 20000<>22136 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(16..16), + value: Some(20000..22136), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 16, value 22136<>30000 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(16..16), + value: Some(22136..30000), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 15, value >4660 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(15..15), + value: Some(4660..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 16, value <22137 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(16..16), + value: Some(std::u16::MIN..22137), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 16, value <22137 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(16..16), + value: Some(std::u16::MIN..22137), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 17, value 39612 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(17..17), + value: Some(39612..39612), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 17, value 30000<>39613 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(17..17), + value: Some(30000..39613), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 15, value 4659<>5000 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(15..15), + value: Some(4659..5000), + ..Default::default() + } + ), + 1 + ); + // access write holding, address 17, value >39611 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + access_type: Some(AccessType::WRITE | AccessType::HOLDING), + address: Some(17..17), + value: Some(39611..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // unit 12 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(12..12), + ..Default::default() + } + ), + 1 + ); + // unit 5<>9 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(5..9), + ..Default::default() + } + ), + 1 + ); + // unit 11<>15 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(11..15), + ..Default::default() + } + ), + 1 + ); + // unit >11 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(11..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // unit <9 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(std::u16::MIN..9), + ..Default::default() + } + ), + 1 + ); + // unit 10 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 5<>15 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(5..15), + ..Default::default() + } + ), + 1 + ); + // unit >9 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(9..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // unit <11 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + unit_id: Some(std::u16::MIN..11), + ..Default::default() + } + ), + 1 + ); + // unit 10, function 20 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + function: Some(FunctionCode::RdFileRec), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 11, function 20 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + function: Some(FunctionCode::RdFileRec), + unit_id: Some(11..11), + ..Default::default() + } + ), + 1 + ); + // unit 11, function 23 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + function: Some(FunctionCode::RdWrMultRegs), + unit_id: Some(11..11), + ..Default::default() + } + ), + 1 + ); + // unit 11, function public + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED), + unit_id: Some(11..11), + ..Default::default() + } + ), + 1 + ); + // unit 10, function user + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + category: Some(Flags::from(CodeCategory::USER_DEFINED)), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 10, function 23 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + function: Some(FunctionCode::RdWrMultRegs), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 10, function public + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 10, function !user + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[0], + &DetectModbusRust { + category: Some(!CodeCategory::USER_DEFINED), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + + // Force Listen Only Mode + assert_eq!( + modbus.parse( + &[ + 0x0A, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x08, // Function code + 0x00, 0x04, // Sub-function code + 0x00, 0x00 // Data + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 2); + // function 8, subfunction 4 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[1], + &DetectModbusRust { + function: Some(FunctionCode::Diagnostic), + subfunction: Some(4), + ..Default::default() + } + ), + 1 + ); + + // Encapsulated Interface Transport (MEI) + assert_eq!( + modbus.parse( + &[ + 0x00, 0x10, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x05, // Length + 0x00, // Unit ID + 0x2B, // Function code + 0x0F, // MEI Type + 0x00, 0x00 // Data + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 3); + // function reserved + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[2], + &DetectModbusRust { + category: Some(Flags::from(CodeCategory::RESERVED)), + ..Default::default() + } + ), + 1 + ); + + // Unassigned/Unknown function + assert_eq!( + modbus.parse( + &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x02, // Length + 0x00, // Unit ID + 0x12 // Function code + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 4); + // function !assigned + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[3], + &DetectModbusRust { + category: Some(!CodeCategory::PUBLIC_ASSIGNED), + ..Default::default() + } + ), + 1 + ); + + // Read Coils request + assert_eq!( + modbus.parse( + &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x0a, // Unit ID + 0x01, // Function code + 0x78, 0x90, // Starting Address + 0x00, 0x13 // Quantity of coils + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 5); + // access read + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + ..Default::default() + } + ), + 1 + ); + // access read, address 30870 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + address: Some(30870..30870), + ..Default::default() + } + ), + 1 + ); + // unit 10, access read, address 30863 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + unit_id: Some(10..10), + address: Some(30863..30863), + ..Default::default() + } + ), + 1 + ); + // unit 11, access read, address 30870 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + unit_id: Some(11..11), + address: Some(30870..30870), + ..Default::default() + } + ), + 1 + ); + // unit 11, access read, address 30863 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + unit_id: Some(11..11), + address: Some(30863..30863), + ..Default::default() + } + ), + 1 + ); + // unit 10, access write + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::WRITE)), + unit_id: Some(10..10), + ..Default::default() + } + ), + 1 + ); + // unit 10, access read, address 30870 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[4], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + unit_id: Some(10..10), + address: Some(30870..30870), + ..Default::default() + } + ), + 1 + ); + + // Read Inputs Register request + assert_eq!( + modbus.parse( + &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x04, // Function code + 0x00, 0x08, // Starting Address + 0x00, 0x60 // Quantity of Registers + ], + Direction::ToServer + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 6); + // access read input + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + ..Default::default() + } + ), + 1 + ); + // access read input, address <9 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(std::u16::MIN..9), + ..Default::default() + } + ), + 1 + ); + // access read input, address 5<>9 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(5..9), + ..Default::default() + } + ), + 1 + ); + // access read input, address >104 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(104..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // access read input, address 104<>110 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(104..110), + ..Default::default() + } + ), + 1 + ); + // access read input, address 9 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(9..9), + ..Default::default() + } + ), + 1 + ); + // access read input, address <10 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(std::u16::MIN..10), + ..Default::default() + } + ), + 1 + ); + // access read input, address 5<>10 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(5..10), + ..Default::default() + } + ), + 1 + ); + // access read input, address >103 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(103..std::u16::MAX), + ..Default::default() + } + ), + 1 + ); + // access read input, address 103<>110 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(103..110), + ..Default::default() + } + ), + 1 + ); + // access read input, address 104 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[5], + &DetectModbusRust { + access_type: Some(AccessType::READ | AccessType::INPUT), + address: Some(104..104), + ..Default::default() + } + ), + 1 + ); + + // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace + // Read Coils Response + assert_eq!( + modbus.parse( + &[ + 0x00, 0x01, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x04, // Length + 0x0a, // Unit ID + 0x01, // Function code + 0x01, // Count + 0x00, // Data + ], + Direction::ToClient + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 7); + // function 1 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[6], + &DetectModbusRust { + function: Some(FunctionCode::RdCoils), + ..Default::default() + } + ), + 1 + ); + // access read, address 104 + // Fails because there was no request, and the address is not retrievable + // from the response. + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[6], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + address: Some(104..104), + ..Default::default() + } + ), + 1 + ); + + // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace + // Write Single Register Response + assert_eq!( + modbus.parse( + &[ + 0x00, 0x01, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x0a, // Unit ID + 0x06, // Function code + 0x00, 0x05, // Starting address + 0x00, 0x0b // Data + ], + Direction::ToClient + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 8); + // function 6 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[7], + &DetectModbusRust { + function: Some(FunctionCode::WrSingleReg), + ..Default::default() + } + ), + 1 + ); + // access write, address 10 + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[7], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::WRITE)), + address: Some(10..10), + ..Default::default() + } + ), + 1 + ); + + // Origin: https://github.com/bro/bro/blob/master/testing/btest/Traces/modbus/modbus.trace + // Write Single Register Response + assert_eq!( + modbus.parse( + &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x0a, // Unit ID + 0x08, // Function code + 0x00, 0x0a, // Diagnostic code + 0x00, 0x00 // Data + ], + Direction::ToClient + ), + AppLayerResult::ok() + ); + assert_eq!(modbus.transactions.len(), 9); + // function 8 + assert_eq!( + rs_modbus_inspect( + &modbus.transactions[8], + &DetectModbusRust { + function: Some(FunctionCode::Diagnostic), + ..Default::default() + } + ), + 1 + ); + // access read + assert_ne!( + rs_modbus_inspect( + &modbus.transactions[8], + &DetectModbusRust { + access_type: Some(Flags::from(AccessType::READ)), + ..Default::default() + } + ), + 1 + ); + } +} diff --git a/rust/src/modbus/log.rs b/rust/src/modbus/log.rs new file mode 100644 index 0000000..6724291 --- /dev/null +++ b/rust/src/modbus/log.rs @@ -0,0 +1,148 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::modbus::ModbusTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +use sawp_modbus::{Data, Message, Read, Write}; + +#[no_mangle] +pub extern "C" fn rs_modbus_to_json(tx: &mut ModbusTransaction, js: &mut JsonBuilder) -> bool { + log(tx, js).is_ok() +} + +/// populate a json object with transactional information, for logging +fn log(tx: &ModbusTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("modbus")?; + js.set_uint("id", tx.id)?; + + if let Some(req) = &tx.request { + js.open_object("request")?; + log_message(req, js)?; + js.close()?; + } + + if let Some(resp) = &tx.response { + js.open_object("response")?; + log_message(resp, js)?; + js.close()?; + } + + js.close()?; + Ok(()) +} + +fn log_message(msg: &Message, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.set_uint("transaction_id", msg.transaction_id.into())?; + js.set_uint("protocol_id", msg.protocol_id.into())?; + js.set_uint("unit_id", msg.unit_id.into())?; + js.set_uint("function_raw", msg.function.raw.into())?; + js.set_string("function_code", &msg.function.code.to_string())?; + js.set_string("access_type", &msg.access_type.to_string())?; + js.set_string("category", &msg.category.to_string())?; + js.set_string("error_flags", &msg.error_flags.to_string())?; + + match &msg.data { + Data::Exception(exc) => { + js.open_object("exception")?; + js.set_uint("raw", exc.raw.into())?; + js.set_string("code", &exc.code.to_string())?; + js.close()?; + } + Data::Diagnostic { func, data } => { + js.open_object("diagnostic")?; + js.set_uint("raw", func.raw.into())?; + js.set_string("code", &func.code.to_string())?; + js.set_string_from_bytes("data", data)?; + js.close()?; + } + Data::MEI { mei_type, data } => { + js.open_object("mei")?; + js.set_uint("raw", mei_type.raw.into())?; + js.set_string("code", &mei_type.code.to_string())?; + js.set_string_from_bytes("data", data)?; + js.close()?; + } + Data::Read(read) => { + js.open_object("read")?; + log_read(read, js)?; + js.close()?; + } + Data::Write(write) => { + js.open_object("write")?; + log_write(write, js)?; + js.close()?; + } + Data::ReadWrite { read, write } => { + js.open_object("read")?; + log_read(read, js)?; + js.close()?; + js.open_object("write")?; + log_write(write, js)?; + js.close()?; + } + Data::ByteVec(data) => { + js.set_string_from_bytes("data", data)?; + } + Data::Empty => {} + } + + Ok(()) +} + +fn log_read(read: &Read, js: &mut JsonBuilder) -> Result<(), JsonError> { + match read { + Read::Request { address, quantity } => { + js.set_uint("address", (*address).into())?; + js.set_uint("quantity", (*quantity).into())?; + } + Read::Response(data) => { + js.set_string_from_bytes("data", data)?; + } + } + + Ok(()) +} + +fn log_write(write: &Write, js: &mut JsonBuilder) -> Result<(), JsonError> { + match write { + Write::MultReq { + address, + quantity, + data, + } => { + js.set_uint("address", (*address).into())?; + js.set_uint("quantity", (*quantity).into())?; + js.set_string_from_bytes("data", data)?; + } + Write::Mask { + address, + and_mask, + or_mask, + } => { + js.set_uint("address", (*address).into())?; + js.set_uint("and_mask", (*and_mask).into())?; + js.set_uint("or_mask", (*or_mask).into())?; + } + Write::Other { address, data } => { + js.set_uint("address", (*address).into())?; + js.set_uint("data", (*data).into())?; + } + } + + Ok(()) +} diff --git a/rust/src/modbus/mod.rs b/rust/src/modbus/mod.rs new file mode 100644 index 0000000..6f3c434 --- /dev/null +++ b/rust/src/modbus/mod.rs @@ -0,0 +1,22 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Modbus application layer, logger, parser and detection module. + +pub mod detect; +pub mod log; +pub mod modbus; diff --git a/rust/src/modbus/modbus.rs b/rust/src/modbus/modbus.rs new file mode 100644 index 0000000..246e9ca --- /dev/null +++ b/rust/src/modbus/modbus.rs @@ -0,0 +1,1458 @@ +/* Copyright (C) 2021 Open Information Security Foundation +* +* You can copy, redistribute or modify this Program under the terms of +* the GNU General Public License version 2 as published by the Free +* Software Foundation. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* version 2 along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +* 02110-1301, USA. +*/ +use crate::applayer::{self, *}; +use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; + +use std::ffi::CString; + +use sawp::error::Error as SawpError; +use sawp::error::ErrorKind as SawpErrorKind; +use sawp::parser::{Direction, Parse}; +use sawp::probe::{Probe, Status}; +use sawp_modbus::{self, AccessType, ErrorFlags, Flags, Message}; + +pub const REQUEST_FLOOD: usize = 500; // Default unreplied Modbus requests are considered a flood +pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus { probe_strict: true }; + +static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent)] +enum ModbusEvent { + UnsolicitedResponse, + InvalidFunctionCode, + InvalidLength, + InvalidValue, + InvalidExceptionCode, + ValueMismatch, + Flooded, + InvalidProtocolId, +} +pub struct ModbusTransaction { + pub id: u64, + + pub request: Option<Message>, + pub response: Option<Message>, + + pub tx_data: AppLayerTxData, +} + +impl Transaction for ModbusTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl ModbusTransaction { + pub fn new(id: u64) -> Self { + Self { + id, + request: None, + response: None, + tx_data: AppLayerTxData::new(), + } + } + + fn set_event(&mut self, event: ModbusEvent) { + self.tx_data.set_event(event as u8); + } + + fn set_events_from_flags(&mut self, flags: &Flags<ErrorFlags>) { + if flags.intersects(ErrorFlags::FUNC_CODE) { + self.set_event(ModbusEvent::InvalidFunctionCode); + } + if flags.intersects(ErrorFlags::DATA_VALUE) { + self.set_event(ModbusEvent::InvalidValue); + } + if flags.intersects(ErrorFlags::DATA_LENGTH) { + self.set_event(ModbusEvent::InvalidLength); + } + if flags.intersects(ErrorFlags::EXC_CODE) { + self.set_event(ModbusEvent::InvalidExceptionCode); + } + if flags.intersects(ErrorFlags::PROTO_ID) { + self.set_event(ModbusEvent::InvalidProtocolId); + } + } +} + +#[derive(Default)] +pub struct ModbusState { + state_data: AppLayerStateData, + pub transactions: Vec<ModbusTransaction>, + tx_id: u64, + givenup: bool, // Indicates flood +} + +impl State<ModbusTransaction> for ModbusState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&ModbusTransaction> { + self.transactions.get(index) + } +} + +impl ModbusState { + pub fn new() -> Self { + Default::default() + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> { + self.transactions.iter_mut().find(|tx| tx.id == tx_id + 1) + } + + /// Searches the requests in order to find one matching the given response. Returns the matching + /// transaction, if it exists + pub fn find_request_and_validate( + &mut self, resp: &mut Message, + ) -> Option<&mut ModbusTransaction> { + for tx in &mut self.transactions { + if let Some(req) = &tx.request { + if tx.response.is_none() && resp.matches(req) { + return Some(tx); + } + } + } + None + } + + /// Searches the responses in order to find one matching the given request. Returns the matching + /// transaction, if it exists + pub fn find_response_and_validate( + &mut self, req: &mut Message, + ) -> Option<&mut ModbusTransaction> { + for tx in &mut self.transactions { + if let Some(resp) = &tx.response { + if tx.request.is_none() && req.matches(resp) { + return Some(tx); + } + } + } + None + } + + pub fn new_tx(&mut self) -> Option<ModbusTransaction> { + // Check flood limit + if self.givenup { + return None; + } + + self.tx_id += 1; + let mut tx = ModbusTransaction::new(self.tx_id); + + if REQUEST_FLOOD != 0 && self.transactions.len() >= REQUEST_FLOOD { + tx.set_event(ModbusEvent::Flooded); + self.givenup = true; + } + + Some(tx) + } + + pub fn free_tx(&mut self, tx_id: u64) { + if let Some(index) = self.transactions.iter().position(|tx| tx.id == tx_id + 1) { + self.transactions.remove(index); + + // Check flood limit + if self.givenup && REQUEST_FLOOD != 0 && self.transactions.len() < REQUEST_FLOOD { + self.givenup = false; + } + } + } + + pub fn parse(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { + let mut rest = input; + while !rest.is_empty() { + match MODBUS_PARSER.parse(rest, direction.clone()) { + Ok((inner_rest, Some(mut msg))) => { + match direction { + Direction::ToServer | Direction::Unknown => { + match self.find_response_and_validate(&mut msg) { + Some(tx) => { + tx.set_events_from_flags(&msg.error_flags); + tx.request = Some(msg); + } + None => { + let mut tx = match self.new_tx() { + Some(tx) => tx, + None => return AppLayerResult::ok(), + }; + tx.set_events_from_flags(&msg.error_flags); + tx.request = Some(msg); + self.transactions.push(tx); + } + } + } + Direction::ToClient => match self.find_request_and_validate(&mut msg) { + Some(tx) => { + if msg + .access_type + .intersects(AccessType::READ | AccessType::WRITE) + && msg.error_flags.intersects( + ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, + ) + { + tx.set_event(ModbusEvent::ValueMismatch); + } else { + tx.set_events_from_flags(&msg.error_flags); + } + tx.response = Some(msg); + } + None => { + let mut tx = match self.new_tx() { + Some(tx) => tx, + None => return AppLayerResult::ok(), + }; + if msg + .access_type + .intersects(AccessType::READ | AccessType::WRITE) + && msg.error_flags.intersects( + ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, + ) + { + tx.set_event(ModbusEvent::ValueMismatch); + } else { + tx.set_events_from_flags(&msg.error_flags); + } + tx.response = Some(msg); + tx.set_event(ModbusEvent::UnsolicitedResponse); + self.transactions.push(tx); + } + }, + } + + if inner_rest.len() >= rest.len() { + return AppLayerResult::err(); + } + rest = inner_rest; + } + Ok((inner_rest, None)) => { + return AppLayerResult::incomplete( + (input.len() - inner_rest.len()) as u32, + inner_rest.len() as u32 + 1, + ); + } + Err(SawpError { + kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)), + }) => { + return AppLayerResult::incomplete( + (input.len() - rest.len()) as u32, + (rest.len() + needed.get()) as u32, + ); + } + Err(SawpError { + kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown), + }) => { + return AppLayerResult::incomplete( + (input.len() - rest.len()) as u32, + rest.len() as u32 + 1, + ); + } + Err(_) => return AppLayerResult::err(), + } + } + AppLayerResult::ok() + } +} + +/// Probe input to see if it looks like Modbus. +#[no_mangle] +pub extern "C" fn rs_modbus_probe( + _flow: *const core::Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8, +) -> AppProto { + let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, len as usize) }; + match MODBUS_PARSER.probe(slice, Direction::Unknown) { + Status::Recognized => unsafe { ALPROTO_MODBUS }, + Status::Incomplete => ALPROTO_UNKNOWN, + Status::Unrecognized => unsafe { ALPROTO_FAILED }, + } +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + Box::into_raw(Box::new(ModbusState::new())) as *mut std::os::raw::c_void +} + +#[no_mangle] +pub extern "C" fn rs_modbus_state_free(state: *mut std::os::raw::c_void) { + let _state: Box<ModbusState> = unsafe { Box::from_raw(state as *mut ModbusState) }; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, ModbusState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_parse_request( + _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let buf = stream_slice.as_slice(); + if buf.is_empty() { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } + } + + let state = cast_pointer!(state, ModbusState); + state.parse(buf, Direction::ToServer) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_parse_response( + _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let buf = stream_slice.as_slice(); + if buf.is_empty() { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } + } + + let state = cast_pointer!(state, ModbusState); + state.parse(buf, Direction::ToClient) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, ModbusState); + state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, ModbusState); + match state.get_tx(tx_id) { + Some(tx) => (tx as *mut ModbusTransaction) as *mut std::os::raw::c_void, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, ModbusTransaction); + tx.response.is_some() as std::os::raw::c_int +} + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_state_get_tx_data( + tx: *mut std::os::raw::c_void, +) -> *mut AppLayerTxData { + let tx = cast_pointer!(tx, ModbusTransaction); + &mut tx.tx_data +} + +export_state_data_get!(rs_modbus_get_state_data, ModbusState); + +#[no_mangle] +pub unsafe extern "C" fn rs_modbus_register_parser() { + let default_port = std::ffi::CString::new("[502]").unwrap(); + let parser = RustParser { + name: b"modbus\0".as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_modbus_probe), + probe_tc: Some(rs_modbus_probe), + min_depth: 0, + max_depth: 16, + state_new: rs_modbus_state_new, + state_free: rs_modbus_state_free, + tx_free: rs_modbus_state_tx_free, + parse_ts: rs_modbus_parse_request, + parse_tc: rs_modbus_parse_response, + get_tx_count: rs_modbus_state_get_tx_count, + get_tx: rs_modbus_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_modbus_tx_get_alstate_progress, + get_eventinfo: Some(ModbusEvent::get_event_info), + get_eventinfo_byid: Some(ModbusEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<ModbusState, ModbusTransaction>), + get_tx_data: rs_modbus_state_get_tx_data, + get_state_data: rs_modbus_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabledDefault(ip_proto_str.as_ptr(), parser.name, false) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_MODBUS = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } +} + +// This struct and accessor functions are used for app-layer-modbus.c tests. +pub mod test { + use super::ModbusState; + use sawp_modbus::{Data, Message, Read, Write}; + use std::ffi::c_void; + #[repr(C)] + pub struct ModbusMessage(*const c_void); + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_function(msg: *const ModbusMessage) -> u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + msg.function.raw + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_subfunction(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Diagnostic { func, data: _ } = &msg.data { + func.raw + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_read_request_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Read(Read::Request { + address, + quantity: _, + }) = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_read_request_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Read(Read::Request { + address: _, + quantity, + }) = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: + Read::Request { + address, + quantity: _, + }, + write: _, + } = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: + Read::Request { + address: _, + quantity, + }, + write: _, + } = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address, + quantity: _, + data: _, + }, + } = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address: _, + quantity, + data: _, + }, + } = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ReadWrite { + read: _, + write: + Write::MultReq { + address: _, + quantity: _, + data, + }, + } = &msg.data + { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_address( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address, + quantity: _, + data: _, + }) = &msg.data + { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_quantity( + msg: *const ModbusMessage, + ) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address: _, + quantity, + data: _, + }) = &msg.data + { + *quantity + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::MultReq { + address: _, + quantity: _, + data, + }) = &msg.data + { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_and_mask(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Mask { + address: _, + and_mask, + or_mask: _, + }) = &msg.data + { + *and_mask + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_or_mask(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Mask { + address: _, + and_mask: _, + or_mask, + }) = &msg.data + { + *or_mask + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_address(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Other { address, data: _ }) = &msg.data { + *address + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_write_data(msg: *const ModbusMessage) -> u16 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::Write(Write::Other { address: _, data }) = &msg.data { + *data + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_message_get_bytevec_data( + msg: *const ModbusMessage, data_len: *mut usize, + ) -> *const u8 { + let msg = msg.as_ref().unwrap().0 as *const Message; + let msg = msg.as_ref().unwrap(); + if let Data::ByteVec(data) = &msg.data { + *data_len = data.len(); + data.as_slice().as_ptr() + } else { + panic!("wrong modbus message data type"); + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_state_get_tx_request( + state: *mut std::os::raw::c_void, tx_id: u64, + ) -> ModbusMessage { + let state = cast_pointer!(state, ModbusState); + if let Some(tx) = state.get_tx(tx_id) { + if let Some(request) = &tx.request { + ModbusMessage((request as *const Message) as *const c_void) + } else { + ModbusMessage(std::ptr::null()) + } + } else { + ModbusMessage(std::ptr::null()) + } + } + + #[no_mangle] + pub unsafe extern "C" fn rs_modbus_state_get_tx_response( + state: *mut std::os::raw::c_void, tx_id: u64, + ) -> ModbusMessage { + let state = cast_pointer!(state, ModbusState); + if let Some(tx) = state.get_tx(tx_id) { + if let Some(response) = &tx.response { + ModbusMessage((response as *const Message) as *const c_void) + } else { + ModbusMessage(std::ptr::null()) + } + } else { + ModbusMessage(std::ptr::null()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sawp_modbus::{ + Data, Diagnostic, DiagnosticSubfunction, Exception, ExceptionCode, FunctionCode, Read, + Write, + }; + + const INVALID_FUNC_CODE: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x02, // Length + 0x00, // Unit ID + 0x00, // Function code + ]; + + const RD_COILS_REQ: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x78, 0x90, // Starting Address + 0x00, 0x13, // Quantity of coils + ]; + + const RD_COILS_RESP: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x03, // Byte count + 0xCD, 0x6B, 0x05, // Coil Status + ]; + + const RD_COILS_ERR_RESP: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x03, // Length + 0x00, // Unit ID + 0x81, // Function code + 0xFF, // Exception code + ]; + + const WR_SINGLE_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + 0x00, 0x03, // Register Value + ]; + + const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x04, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + ]; + + const WR_SINGLE_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x06, // Function code + 0x00, 0x01, // Register Address + 0x00, 0x03, // Register Value + ]; + + const WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x0B, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + 0x04, // Byte count + 0x00, 0x0A, // Registers Value + 0x01, 0x02, + ]; + + const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x02, // Length + 0x00, // Unit ID + 0x10, // Function code + ]; + + const WR_MULT_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + ]; + + const MASK_WR_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x08, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + 0x00, 0x25, // Or_Mask + ]; + + const INVALID_MASK_WR_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + ]; + + const MASK_WR_REG_RESP: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x08, // Length + 0x00, // Unit ID + 0x16, // Function code + 0x00, 0x04, // Reference Address + 0x00, 0xF2, // And_Mask + 0x00, 0x25, // Or_Mask + ]; + + const RD_WR_MULT_REG_REQ: &[u8] = &[ + 0x12, 0x34, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x11, // Length + 0x00, // Unit ID + 0x17, // Function code + 0x00, 0x03, // Read Starting Address + 0x00, 0x06, // Quantity to Read + 0x00, 0x0E, // Write Starting Address + 0x00, 0x03, // Quantity to Write + 0x06, // Write Byte count + 0x12, 0x34, // Write Registers Value + 0x56, 0x78, 0x9A, 0xBC, + ]; + + // Mismatch value in Byte count 0x0B instead of 0x0C + const RD_WR_MULT_REG_RESP: &[u8] = &[ + 0x12, 0x34, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x0E, // Length + 0x00, // Unit ID + 0x17, // Function code + 0x0B, // Byte count + 0x00, 0xFE, // Read Registers Value + 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, + ]; + + const FORCE_LISTEN_ONLY_MODE: &[u8] = &[ + 0x0A, 0x00, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x08, // Function code + 0x00, 0x04, // Sub-function code + 0x00, 0x00, // Data + ]; + + const INVALID_PROTO_REQ: &[u8] = &[ + 0x00, 0x00, // Transaction ID + 0x00, 0x01, // Protocol ID + 0x00, 0x06, // Length + 0x00, // Unit ID + 0x01, // Function code + 0x78, 0x90, // Starting Address + 0x00, 0x13, // Quantity of coils + ]; + + const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0x00, 0x09, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x00, 0x02, // Quantity of Registers + 0x04, // Byte count + 0x00, 0x0A, // Registers Value + 0x01, 0x02, + ]; + + const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[ + 0x00, 0x0A, // Transaction ID + 0x00, 0x00, // Protocol ID + 0xff, 0xfa, // Length + 0x00, // Unit ID + 0x10, // Function code + 0x00, 0x01, // Starting Address + 0x7f, 0xf9, // Quantity of Registers + 0xff, // Byte count + ]; + + #[test] + fn read_coils() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + } + + #[test] + fn write_multiple_registers() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a, 0x01, 0x02], + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(WR_MULT_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0002 + }) + ); + } + + #[test] + fn read_write_multiple_registers() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); + assert_eq!( + msg.data, + Data::ReadWrite { + read: Read::Request { + address: 0x0003, + quantity: 0x0006, + }, + write: Write::MultReq { + address: 0x000e, + quantity: 0x0003, + data: vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc] + } + } + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_WR_MULT_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); + assert_eq!( + msg.data, + Data::Read(Read::Response(vec![ + 0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, + ])) + ); + } + + #[test] + fn force_listen_only_mode() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(FORCE_LISTEN_ONLY_MODE, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::Diagnostic); + assert_eq!( + msg.data, + Data::Diagnostic { + func: Diagnostic { + raw: 4, + code: DiagnosticSubfunction::ForceListenOnlyMode + }, + data: vec![0x00, 0x00] + } + ); + } + + #[test] + fn invalid_protocol_version() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(INVALID_PROTO_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.error_flags, ErrorFlags::PROTO_ID); + } + + #[test] + fn unsolicited_response() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + } + + #[test] + fn invalid_length_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::incomplete(15, 4), + state.parse(INVALID_LEN_WR_MULT_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a] + }) + ); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + } + + #[test] + fn exception_code_invalid() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_ERR_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!( + msg.data, + Data::Exception(Exception { + raw: 255, + code: ExceptionCode::Unknown + }) + ); + assert_eq!(msg.error_flags, ErrorFlags::EXC_CODE); + } + + #[test] + fn fragmentation_1_adu_in_2_tcp_packets() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::incomplete(0, 12), + state.parse( + &RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)], + Direction::ToServer + ) + ); + assert_eq!(state.transactions.len(), 0); + assert_eq!( + AppLayerResult::ok(), + state.parse(RD_COILS_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + assert!(&tx.request.is_some()); + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + } + + #[test] + fn fragmentation_2_adu_in_1_tcp_packet() { + let req = [RD_COILS_REQ, WR_MULT_REG_REQ].concat(); + let resp = [RD_COILS_RESP, WR_MULT_REG_RESP].concat(); + + let mut state = ModbusState::new(); + assert_eq!(AppLayerResult::ok(), state.parse(&req, Direction::ToServer)); + assert_eq!(state.transactions.len(), 2); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!( + msg.data, + Data::Read(Read::Request { + address: 0x7890, + quantity: 0x0013 + }) + ); + + let tx = &state.transactions[1]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::MultReq { + address: 0x0001, + quantity: 0x0002, + data: vec![0x00, 0x0a, 0x01, 0x02] + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(&resp, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 2); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::RdCoils); + assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); + + let tx = &state.transactions[1]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0002 + }) + ); + } + + #[test] + fn exceeded_length_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(EXCEEDED_LEN_WR_MULT_REG_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + } + + #[test] + fn invalid_pdu_len_req() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(INVALID_PDU_WR_MULT_REG_REQ, Direction::ToServer) + ); + + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrMultRegs); + assert_eq!(msg.data, Data::ByteVec(vec![])); + } + + #[test] + fn mask_write_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(MASK_WR_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(MASK_WR_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + } + + #[test] + fn write_single_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(WR_SINGLE_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + + assert_eq!( + AppLayerResult::ok(), + state.parse(WR_SINGLE_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + } + + #[test] + fn invalid_mask_write_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(INVALID_MASK_WR_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x04, 0x00, 0xF2])); + + assert_eq!( + AppLayerResult::ok(), + state.parse(MASK_WR_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::MaskWrReg); + assert_eq!( + msg.data, + Data::Write(Write::Mask { + address: 0x0004, + and_mask: 0x00f2, + or_mask: 0x0025 + }) + ); + } + + #[test] + fn invalid_write_single_register_request() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(INVALID_WR_SINGLE_REG_REQ, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); + assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x01])); + + assert_eq!( + AppLayerResult::ok(), + state.parse(WR_SINGLE_REG_RESP, Direction::ToClient) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.response.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::WrSingleReg); + assert_eq!( + msg.data, + Data::Write(Write::Other { + address: 0x0001, + data: 0x0003 + }) + ); + } + + #[test] + fn invalid_function_code() { + let mut state = ModbusState::new(); + assert_eq!( + AppLayerResult::ok(), + state.parse(INVALID_FUNC_CODE, Direction::ToServer) + ); + assert_eq!(state.transactions.len(), 1); + + let tx = &state.transactions[0]; + let msg = tx.request.as_ref().unwrap(); + assert_eq!(msg.function.code, FunctionCode::Unknown); + assert_eq!(msg.data, Data::ByteVec(vec![])); + } +} diff --git a/rust/src/mqtt/detect.rs b/rust/src/mqtt/detect.rs new file mode 100644 index 0000000..b47a84f --- /dev/null +++ b/rust/src/mqtt/detect.rs @@ -0,0 +1,520 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use crate::mqtt::mqtt::{MQTTState, MQTTTransaction}; +use crate::mqtt::mqtt_message::{MQTTOperation, MQTTTypeCode}; +use std::ffi::CStr; +use std::ptr; +use std::str::FromStr; + +#[derive(FromPrimitive, Debug, Copy, Clone, PartialOrd, PartialEq, Eq)] +#[allow(non_camel_case_types)] +#[repr(u8)] +pub enum MQTTFlagState { + MQTT_DONT_CARE = 0, + MQTT_MUST_BE_SET = 1, + MQTT_CANT_BE_SET = 2, +} + +#[inline] +fn check_flag_state(flag_state: MQTTFlagState, flag_value: bool, ok: &mut bool) { + match flag_state { + MQTTFlagState::MQTT_MUST_BE_SET => { + if !flag_value { + *ok = false; + } + } + MQTTFlagState::MQTT_CANT_BE_SET => { + if flag_value { + *ok = false; + } + } + _ => {} + } +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_has_type(tx: &MQTTTransaction, mtype: u8) -> u8 { + for msg in tx.msg.iter() { + if mtype == msg.header.message_type as u8 { + return 1; + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_cstr_message_code( + str: *const std::os::raw::c_char, +) -> std::os::raw::c_int { + let msgtype: &CStr = CStr::from_ptr(str); + if let Ok(s) = msgtype.to_str() { + if let Ok(x) = MQTTTypeCode::from_str(s) { + return x as i32; + } + } + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_has_flags( + tx: &MQTTTransaction, qretain: MQTTFlagState, qdup: MQTTFlagState, +) -> u8 { + for msg in tx.msg.iter() { + let mut ok = true; + check_flag_state(qretain, msg.header.retain, &mut ok); + check_flag_state(qdup, msg.header.dup_flag, &mut ok); + if ok { + return 1; + } + } + + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_has_qos(tx: &MQTTTransaction, qos: u8) -> u8 { + for msg in tx.msg.iter() { + if qos == msg.header.qos_level { + return 1; + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_get_protocol_version(state: &MQTTState) -> u8 { + return state.protocol_version; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_has_connect_flags( + tx: &MQTTTransaction, username: MQTTFlagState, password: MQTTFlagState, will: MQTTFlagState, + will_retain: MQTTFlagState, clean_session: MQTTFlagState, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + let mut ok = true; + check_flag_state(username, cv.username_flag, &mut ok); + check_flag_state(password, cv.password_flag, &mut ok); + check_flag_state(will, cv.will_flag, &mut ok); + check_flag_state(will_retain, cv.will_retain, &mut ok); + check_flag_state(clean_session, cv.clean_session, &mut ok); + if ok { + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connect_clientid( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + let p = &cv.client_id; + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connect_username( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + if let Some(p) = &cv.username { + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connect_password( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + if let Some(p) = &cv.password { + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connect_willtopic( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + if let Some(p) = &cv.will_topic { + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connect_willmessage( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNECT(ref cv) = msg.op { + if let Some(p) = &cv.will_message { + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_connack_sessionpresent( + tx: &MQTTTransaction, session_present: *mut bool, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::CONNACK(ref ca) = msg.op { + *session_present = ca.session_present; + return 1; + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_publish_topic( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::PUBLISH(ref pubv) = msg.op { + let p = &pubv.topic; + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_publish_message( + tx: &MQTTTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::PUBLISH(ref pubv) = msg.op { + let p = &pubv.message; + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_subscribe_topic( + tx: &MQTTTransaction, i: u32, buf: *mut *const u8, len: *mut u32, +) -> u8 { + let mut offset = 0; + for msg in tx.msg.iter() { + if let MQTTOperation::SUBSCRIBE(ref subv) = msg.op { + if (i as usize) < subv.topics.len() + offset { + let topic = &subv.topics[(i as usize) - offset]; + if !topic.topic_name.is_empty() { + *len = topic.topic_name.len() as u32; + *buf = topic.topic_name.as_ptr(); + return 1; + } + } else { + offset += subv.topics.len(); + } + } + } + + *buf = ptr::null(); + *len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_unsubscribe_topic( + tx: &MQTTTransaction, i: u32, buf: *mut *const u8, len: *mut u32, +) -> u8 { + let mut offset = 0; + for msg in tx.msg.iter() { + if let MQTTOperation::UNSUBSCRIBE(ref unsubv) = msg.op { + if (i as usize) < unsubv.topics.len() + offset { + let topic = &unsubv.topics[(i as usize) - offset]; + if !topic.is_empty() { + *len = topic.len() as u32; + *buf = topic.as_ptr(); + return 1; + } + } else { + offset += unsubv.topics.len(); + } + } + } + + *buf = ptr::null(); + *len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_reason_code(tx: &MQTTTransaction, result: *mut u8) -> u8 { + for msg in tx.msg.iter() { + match msg.op { + MQTTOperation::PUBACK(ref v) + | MQTTOperation::PUBREL(ref v) + | MQTTOperation::PUBREC(ref v) + | MQTTOperation::PUBCOMP(ref v) => { + if let Some(rcode) = v.reason_code { + *result = rcode; + return 1; + } + } + MQTTOperation::AUTH(ref v) => { + *result = v.reason_code; + return 1; + } + MQTTOperation::CONNACK(ref v) => { + *result = v.return_code; + return 1; + } + MQTTOperation::DISCONNECT(ref v) => { + if let Some(rcode) = v.reason_code { + *result = rcode; + return 1; + } + } + _ => return 0, + } + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_tx_unsuback_has_reason_code(tx: &MQTTTransaction, code: u8) -> u8 { + for msg in tx.msg.iter() { + if let MQTTOperation::UNSUBACK(ref unsuback) = msg.op { + if let Some(ref reason_codes) = unsuback.reason_codes { + for rc in reason_codes.iter() { + if *rc == code { + return 1; + } + } + } + } + } + return 0; +} + +#[cfg(test)] +mod test { + use super::*; + use crate::mqtt::mqtt::MQTTTransaction; + use crate::mqtt::mqtt_message::*; + use crate::mqtt::parser::FixedHeader; + use crate::core::Direction; + use std; + + #[test] + fn test_multi_unsubscribe() { + let mut t = MQTTTransaction::new(MQTTMessage { + header: FixedHeader { + message_type: MQTTTypeCode::UNSUBSCRIBE, + dup_flag: false, + qos_level: 0, + retain: false, + remaining_length: 0, + }, + op: MQTTOperation::UNSUBSCRIBE(MQTTUnsubscribeData { + message_id: 1, + topics: vec!["foo".to_string(), "baar".to_string()], + properties: None, + }), + }, Direction::ToServer); + t.msg.push(MQTTMessage { + header: FixedHeader { + message_type: MQTTTypeCode::UNSUBSCRIBE, + dup_flag: false, + qos_level: 0, + retain: false, + remaining_length: 0, + }, + op: MQTTOperation::UNSUBSCRIBE(MQTTUnsubscribeData { + message_id: 1, + topics: vec!["fieee".to_string(), "baaaaz".to_string()], + properties: None, + }), + }); + let mut s: *const u8 = std::ptr::null_mut(); + let mut slen: u32 = 0; + let mut r = unsafe { rs_mqtt_tx_get_unsubscribe_topic(&t, 0, &mut s, &mut slen) }; + assert_eq!(r, 1); + let mut topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "foo"); + r = unsafe { rs_mqtt_tx_get_unsubscribe_topic(&t, 1, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "baar"); + r = unsafe { rs_mqtt_tx_get_unsubscribe_topic(&t, 2, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "fieee"); + r = unsafe { rs_mqtt_tx_get_unsubscribe_topic(&t, 3, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "baaaaz"); + r = unsafe { rs_mqtt_tx_get_unsubscribe_topic(&t, 4, &mut s, &mut slen) }; + assert_eq!(r, 0); + } + + #[test] + fn test_multi_subscribe() { + let mut t = MQTTTransaction::new(MQTTMessage { + header: FixedHeader { + message_type: MQTTTypeCode::SUBSCRIBE, + dup_flag: false, + qos_level: 0, + retain: false, + remaining_length: 0, + }, + op: MQTTOperation::SUBSCRIBE(MQTTSubscribeData { + message_id: 1, + topics: vec![ + MQTTSubscribeTopicData { + topic_name: "foo".to_string(), + qos: 0, + }, + MQTTSubscribeTopicData { + topic_name: "baar".to_string(), + qos: 1, + }, + ], + properties: None, + }), + }, Direction::ToServer); + t.msg.push(MQTTMessage { + header: FixedHeader { + message_type: MQTTTypeCode::SUBSCRIBE, + dup_flag: false, + qos_level: 0, + retain: false, + remaining_length: 0, + }, + op: MQTTOperation::SUBSCRIBE(MQTTSubscribeData { + message_id: 1, + topics: vec![ + MQTTSubscribeTopicData { + topic_name: "fieee".to_string(), + qos: 0, + }, + MQTTSubscribeTopicData { + topic_name: "baaaaz".to_string(), + qos: 1, + }, + ], + properties: None, + }), + }); + let mut s: *const u8 = std::ptr::null_mut(); + let mut slen: u32 = 0; + let mut r = unsafe { rs_mqtt_tx_get_subscribe_topic(&t, 0, &mut s, &mut slen) }; + assert_eq!(r, 1); + let mut topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "foo"); + r = unsafe { rs_mqtt_tx_get_subscribe_topic(&t, 1, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "baar"); + r = unsafe { rs_mqtt_tx_get_subscribe_topic(&t, 2, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "fieee"); + r = unsafe { rs_mqtt_tx_get_subscribe_topic(&t, 3, &mut s, &mut slen) }; + assert_eq!(r, 1); + topic = String::from_utf8_lossy(unsafe { build_slice!(s, slen as usize) }); + assert_eq!(topic, "baaaaz"); + r = unsafe { rs_mqtt_tx_get_subscribe_topic(&t, 4, &mut s, &mut slen) }; + assert_eq!(r, 0); + } +} diff --git a/rust/src/mqtt/logger.rs b/rust/src/mqtt/logger.rs new file mode 100644 index 0000000..af0db17 --- /dev/null +++ b/rust/src/mqtt/logger.rs @@ -0,0 +1,305 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use super::mqtt::MQTTTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::mqtt::mqtt_message::{MQTTOperation, MQTTSubscribeTopicData}; +use crate::mqtt::parser::FixedHeader; +use std; + +pub const MQTT_LOG_PASSWORDS: u32 = BIT_U32!(0); + +#[inline] +fn log_mqtt_topic(js: &mut JsonBuilder, t: &MQTTSubscribeTopicData) -> Result<(), JsonError> { + js.start_object()?; + js.set_string("topic", &t.topic_name)?; + js.set_uint("qos", t.qos as u64)?; + js.close()?; + return Ok(()); +} + +#[inline] +fn log_mqtt_header(js: &mut JsonBuilder, hdr: &FixedHeader) -> Result<(), JsonError> { + js.set_uint("qos", hdr.qos_level as u64)?; + js.set_bool("retain", hdr.retain)?; + js.set_bool("dup", hdr.dup_flag)?; + return Ok(()); +} + +fn log_mqtt(tx: &MQTTTransaction, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("mqtt")?; + for msg in tx.msg.iter() { + match msg.op { + MQTTOperation::CONNECT(ref conn) => { + js.open_object("connect")?; + log_mqtt_header(js, &msg.header)?; + js.set_string("protocol_string", &conn.protocol_string)?; + js.set_uint("protocol_version", conn.protocol_version as u64)?; + js.set_string("client_id", &conn.client_id)?; + js.open_object("flags")?; + js.set_bool("username", conn.username_flag)?; + js.set_bool("password", conn.password_flag)?; + js.set_bool("will_retain", conn.will_retain)?; + js.set_bool("will", conn.will_flag)?; + js.set_bool("clean_session", conn.clean_session)?; + js.close()?; // flags + if let Some(user) = &conn.username { + js.set_string("username", user)?; + } + if flags & MQTT_LOG_PASSWORDS != 0 { + if let Some(pass) = &conn.password { + js.set_string_from_bytes("password", pass)?; + } + } + if conn.will_flag { + js.open_object("will")?; + if let Some(will_topic) = &conn.will_topic { + js.set_string("topic", will_topic)?; + } + if let Some(will_message) = &conn.will_message { + js.set_string_from_bytes("message", will_message)?; + } + if let Some(will_properties) = &conn.will_properties { + js.open_object("properties")?; + for prop in will_properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // will + } + if let Some(properties) = &conn.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // connect + } + MQTTOperation::CONNACK(ref connack) => { + js.open_object("connack")?; + log_mqtt_header(js, &msg.header)?; + js.set_bool("session_present", connack.session_present)?; + js.set_uint("return_code", connack.return_code as u64)?; + if let Some(properties) = &connack.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // connack + } + MQTTOperation::PUBLISH(ref publish) => { + js.open_object("publish")?; + log_mqtt_header(js, &msg.header)?; + js.set_string("topic", &publish.topic)?; + if let Some(message_id) = publish.message_id { + js.set_uint("message_id", message_id as u64)?; + } + js.set_string_from_bytes("message", &publish.message)?; + if let Some(properties) = &publish.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // publish + } + MQTTOperation::PUBACK(ref msgidonly) => { + js.open_object("puback")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", msgidonly.message_id as u64)?; + if let Some(reason_code) = &msgidonly.reason_code { + js.set_uint("reason_code", *reason_code as u64)?; + } + if let Some(properties) = &msgidonly.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // puback + } + MQTTOperation::PUBREC(ref msgidonly) => { + js.open_object("pubrec")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", msgidonly.message_id as u64)?; + if let Some(reason_code) = &msgidonly.reason_code { + js.set_uint("reason_code", *reason_code as u64)?; + } + if let Some(properties) = &msgidonly.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // pubrec + } + MQTTOperation::PUBREL(ref msgidonly) => { + js.open_object("pubrel")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", msgidonly.message_id as u64)?; + if let Some(reason_code) = &msgidonly.reason_code { + js.set_uint("reason_code", *reason_code as u64)?; + } + if let Some(properties) = &msgidonly.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // pubrel + } + MQTTOperation::PUBCOMP(ref msgidonly) => { + js.open_object("pubcomp")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", msgidonly.message_id as u64)?; + if let Some(reason_code) = &msgidonly.reason_code { + js.set_uint("reason_code", *reason_code as u64)?; + } + if let Some(properties) = &msgidonly.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // pubcomp + } + MQTTOperation::SUBSCRIBE(ref subs) => { + js.open_object("subscribe")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", subs.message_id as u64)?; + js.open_array("topics")?; + for t in &subs.topics { + log_mqtt_topic(js, t)?; + } + js.close()?; //topics + if let Some(properties) = &subs.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // subscribe + } + MQTTOperation::SUBACK(ref suback) => { + js.open_object("suback")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", suback.message_id as u64)?; + js.open_array("qos_granted")?; + for t in &suback.qoss { + js.append_uint(*t as u64)?; + } + js.close()?; // qos_granted + js.close()?; // suback + } + MQTTOperation::UNSUBSCRIBE(ref unsub) => { + js.open_object("unsubscribe")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", unsub.message_id as u64)?; + js.open_array("topics")?; + for t in &unsub.topics { + js.append_string(t)?; + } + js.close()?; // topics + js.close()?; // unsubscribe + } + MQTTOperation::UNSUBACK(ref unsuback) => { + js.open_object("unsuback")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("message_id", unsuback.message_id as u64)?; + if let Some(codes) = &unsuback.reason_codes { + if !codes.is_empty() { + js.open_array("reason_codes")?; + for t in codes { + js.append_uint(*t as u64)?; + } + js.close()?; // reason_codes + } + } + js.close()?; // unsuback + } + MQTTOperation::PINGREQ => { + js.open_object("pingreq")?; + log_mqtt_header(js, &msg.header)?; + js.close()?; // pingreq + } + MQTTOperation::PINGRESP => { + js.open_object("pingresp")?; + log_mqtt_header(js, &msg.header)?; + js.close()?; // pingresp + } + MQTTOperation::AUTH(ref auth) => { + js.open_object("auth")?; + log_mqtt_header(js, &msg.header)?; + js.set_uint("reason_code", auth.reason_code as u64)?; + if let Some(properties) = &auth.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // auth + } + MQTTOperation::DISCONNECT(ref disco) => { + js.open_object("disconnect")?; + log_mqtt_header(js, &msg.header)?; + if let Some(reason_code) = &disco.reason_code { + js.set_uint("reason_code", *reason_code as u64)?; + } + if let Some(properties) = &disco.properties { + js.open_object("properties")?; + for prop in properties { + prop.set_json(js)?; + } + js.close()?; // properties + } + js.close()?; // disconnect + } + MQTTOperation::TRUNCATED(ref trunc) => { + js.open_object(&trunc.original_message_type.to_lower_str())?; + log_mqtt_header(js, &msg.header)?; + js.set_bool("truncated", true)?; + js.set_uint("skipped_length", trunc.skipped_length as u64)?; + js.close()?; // truncated + } + MQTTOperation::UNASSIGNED => {} + } + } + js.close()?; // mqtt + + return Ok(()); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_logger_log( + tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, MQTTTransaction); + log_mqtt(tx, flags, js).is_ok() +} diff --git a/rust/src/mqtt/mod.rs b/rust/src/mqtt/mod.rs new file mode 100644 index 0000000..aefcc33 --- /dev/null +++ b/rust/src/mqtt/mod.rs @@ -0,0 +1,25 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! MQTT application layer, detection, logger and parser module. + +pub mod detect; +pub mod logger; +pub mod mqtt; +pub mod mqtt_message; +pub mod mqtt_property; +pub mod parser; diff --git a/rust/src/mqtt/mqtt.rs b/rust/src/mqtt/mqtt.rs new file mode 100644 index 0000000..3f110df --- /dev/null +++ b/rust/src/mqtt/mqtt.rs @@ -0,0 +1,805 @@ +/* Copyright (C) 2020-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use super::mqtt_message::*; +use super::parser::*; +use crate::applayer::*; +use crate::applayer::{self, LoggerFlags}; +use crate::conf::conf_get; +use crate::core::*; +use crate::frames::*; +use nom7::Err; +use std; +use std::collections::VecDeque; +use std::ffi::CString; + +// Used as a special pseudo packet identifier to denote the first CONNECT +// packet in a connection. Note that there is no risk of collision with a +// parsed packet identifier because in the protocol these are only 16 bit +// unsigned. +const MQTT_CONNECT_PKT_ID: u32 = std::u32::MAX; +// Maximum message length in bytes. If the length of a message exceeds +// this value, it will be truncated. Default: 1MB. +static mut MAX_MSG_LEN: u32 = 1048576; + +static mut MQTT_MAX_TX: usize = 1024; + +static mut ALPROTO_MQTT: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerFrameType)] +pub enum MQTTFrameType { + Pdu, + Header, + Data, +} + +#[derive(FromPrimitive, Debug, AppLayerEvent)] +pub enum MQTTEvent { + MissingConnect, + MissingPublish, + MissingSubscribe, + MissingUnsubscribe, + DoubleConnect, + UnintroducedMessage, + InvalidQosLevel, + MissingMsgId, + UnassignedMsgType, + TooManyTransactions, + MalformedTraffic, +} + +#[derive(Debug)] +pub struct MQTTTransaction { + tx_id: u64, + pkt_id: Option<u32>, + pub msg: Vec<MQTTMessage>, + complete: bool, + toclient: bool, + toserver: bool, + + logged: LoggerFlags, + tx_data: applayer::AppLayerTxData, +} + +impl MQTTTransaction { + pub fn new(msg: MQTTMessage, direction: Direction) -> MQTTTransaction { + let mut m = MQTTTransaction::new_empty(direction); + m.msg.push(msg); + return m; + } + + pub fn new_empty(direction: Direction) -> MQTTTransaction { + return MQTTTransaction { + tx_id: 0, + pkt_id: None, + complete: false, + logged: LoggerFlags::new(), + msg: Vec::new(), + toclient: direction.is_to_client(), + toserver: direction.is_to_server(), + tx_data: applayer::AppLayerTxData::for_direction(direction), + }; + } +} + +impl Transaction for MQTTTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +pub struct MQTTState { + state_data: AppLayerStateData, + tx_id: u64, + pub protocol_version: u8, + transactions: VecDeque<MQTTTransaction>, + connected: bool, + skip_request: usize, + skip_response: usize, + max_msg_len: usize, + tx_index_completed: usize, +} + +impl State<MQTTTransaction> for MQTTState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&MQTTTransaction> { + self.transactions.get(index) + } +} + +impl Default for MQTTState { + fn default() -> Self { + Self::new() + } +} + +impl MQTTState { + pub fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + tx_id: 0, + protocol_version: 0, + transactions: VecDeque::new(), + connected: false, + skip_request: 0, + skip_response: 0, + max_msg_len: unsafe { MAX_MSG_LEN as usize }, + tx_index_completed: 0, + } + } + + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&MQTTTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + pub fn get_tx_by_pkt_id(&mut self, pkt_id: u32) -> Option<&mut MQTTTransaction> { + for tx in &mut self.transactions.range_mut(self.tx_index_completed..) { + if !tx.complete { + if let Some(mpktid) = tx.pkt_id { + if mpktid == pkt_id { + return Some(tx); + } + } + } + } + return None; + } + + fn new_tx(&mut self, msg: MQTTMessage, toclient: bool) -> MQTTTransaction { + let direction = if toclient { + Direction::ToClient + } else { + Direction::ToServer + }; + let mut tx = MQTTTransaction::new(msg, direction); + self.tx_id += 1; + tx.tx_id = self.tx_id; + if self.transactions.len() > unsafe { MQTT_MAX_TX } { + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if !tx_old.complete { + tx_old.complete = true; + MQTTState::set_event(tx_old, MQTTEvent::TooManyTransactions); + break; + } + } + self.tx_index_completed = index; + } + return tx; + } + + // Handle a MQTT message depending on the direction and state. + // Note that we are trying to only have one mutable reference to msg + // and its components, however, since we are in a large match operation, + // we cannot pass around and/or store more references or move things + // without having to introduce lifetimes etc. + // This is the reason for the code duplication below. Maybe there is a + // more concise way to do it, but this works for now. + fn handle_msg(&mut self, msg: MQTTMessage, toclient: bool) { + match msg.op { + MQTTOperation::CONNECT(ref conn) => { + self.protocol_version = conn.protocol_version; + let mut tx = self.new_tx(msg, toclient); + tx.pkt_id = Some(MQTT_CONNECT_PKT_ID); + if self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::DoubleConnect); + } + self.transactions.push_back(tx); + } + MQTTOperation::PUBLISH(ref publish) => { + let qos = msg.header.qos_level; + let pkt_id = publish.message_id; + let mut tx = self.new_tx(msg, toclient); + match qos { + 0 => { + // with QOS level 0, we do not need to wait for a + // response + tx.complete = true; + } + 1..=2 => { + if let Some(pkt_id) = pkt_id { + tx.pkt_id = Some(pkt_id as u32); + } else { + MQTTState::set_event(&mut tx, MQTTEvent::MissingMsgId); + } + } + _ => { + MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel); + } + } + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + self.transactions.push_back(tx); + } + MQTTOperation::SUBSCRIBE(ref subscribe) => { + let pkt_id = subscribe.message_id as u32; + let qos = msg.header.qos_level; + let mut tx = self.new_tx(msg, toclient); + match qos { + 0 => { + // with QOS level 0, we do not need to wait for a + // response + tx.complete = true; + } + 1..=2 => { + tx.pkt_id = Some(pkt_id); + } + _ => { + MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel); + } + } + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + self.transactions.push_back(tx); + } + MQTTOperation::UNSUBSCRIBE(ref unsubscribe) => { + let pkt_id = unsubscribe.message_id as u32; + let qos = msg.header.qos_level; + let mut tx = self.new_tx(msg, toclient); + match qos { + 0 => { + // with QOS level 0, we do not need to wait for a + // response + tx.complete = true; + } + 1..=2 => { + tx.pkt_id = Some(pkt_id); + } + _ => { + MQTTState::set_event(&mut tx, MQTTEvent::InvalidQosLevel); + } + } + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + self.transactions.push_back(tx); + } + MQTTOperation::CONNACK(ref _connack) => { + if let Some(tx) = self.get_tx_by_pkt_id(MQTT_CONNECT_PKT_ID) { + tx.msg.push(msg); + tx.complete = true; + tx.pkt_id = None; + self.connected = true; + } else { + let mut tx = self.new_tx(msg, toclient); + MQTTState::set_event(&mut tx, MQTTEvent::MissingConnect); + tx.complete = true; + self.transactions.push_back(tx); + } + } + MQTTOperation::PUBREC(ref v) | MQTTOperation::PUBREL(ref v) => { + if let Some(tx) = self.get_tx_by_pkt_id(v.message_id as u32) { + tx.msg.push(msg); + } else { + let mut tx = self.new_tx(msg, toclient); + MQTTState::set_event(&mut tx, MQTTEvent::MissingPublish); + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + tx.complete = true; + self.transactions.push_back(tx); + } + } + MQTTOperation::PUBACK(ref v) | MQTTOperation::PUBCOMP(ref v) => { + if let Some(tx) = self.get_tx_by_pkt_id(v.message_id as u32) { + tx.msg.push(msg); + tx.complete = true; + tx.pkt_id = None; + } else { + let mut tx = self.new_tx(msg, toclient); + MQTTState::set_event(&mut tx, MQTTEvent::MissingPublish); + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + tx.complete = true; + self.transactions.push_back(tx); + } + } + MQTTOperation::SUBACK(ref suback) => { + if let Some(tx) = self.get_tx_by_pkt_id(suback.message_id as u32) { + tx.msg.push(msg); + tx.complete = true; + tx.pkt_id = None; + } else { + let mut tx = self.new_tx(msg, toclient); + MQTTState::set_event(&mut tx, MQTTEvent::MissingSubscribe); + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + tx.complete = true; + self.transactions.push_back(tx); + } + } + MQTTOperation::UNSUBACK(ref unsuback) => { + if let Some(tx) = self.get_tx_by_pkt_id(unsuback.message_id as u32) { + tx.msg.push(msg); + tx.complete = true; + tx.pkt_id = None; + } else { + let mut tx = self.new_tx(msg, toclient); + MQTTState::set_event(&mut tx, MQTTEvent::MissingUnsubscribe); + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + tx.complete = true; + self.transactions.push_back(tx); + } + } + MQTTOperation::UNASSIGNED => { + let mut tx = self.new_tx(msg, toclient); + tx.complete = true; + MQTTState::set_event(&mut tx, MQTTEvent::UnassignedMsgType); + self.transactions.push_back(tx); + } + MQTTOperation::TRUNCATED(_) => { + let mut tx = self.new_tx(msg, toclient); + tx.complete = true; + self.transactions.push_back(tx); + } + MQTTOperation::AUTH(_) | MQTTOperation::DISCONNECT(_) => { + let mut tx = self.new_tx(msg, toclient); + tx.complete = true; + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + self.transactions.push_back(tx); + } + MQTTOperation::PINGREQ | MQTTOperation::PINGRESP => { + let mut tx = self.new_tx(msg, toclient); + tx.complete = true; + if !self.connected { + MQTTState::set_event(&mut tx, MQTTEvent::UnintroducedMessage); + } + self.transactions.push_back(tx); + } + } + } + + fn parse_request(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + let mut current = input; + + if input.is_empty() { + return AppLayerResult::ok(); + } + + let mut consumed = 0; + SCLogDebug!( + "skip_request {} input len {}", + self.skip_request, + input.len() + ); + if self.skip_request > 0 { + if input.len() <= self.skip_request { + SCLogDebug!("reducing skip_request by {}", input.len()); + self.skip_request -= input.len(); + return AppLayerResult::ok(); + } else { + current = &input[self.skip_request..]; + SCLogDebug!( + "skip end reached, skipping {} :{:?}", + self.skip_request, + current + ); + consumed = self.skip_request; + self.skip_request = 0; + } + } + + while !current.is_empty() { + SCLogDebug!("request: handling {}", current.len()); + match parse_message(current, self.protocol_version, self.max_msg_len) { + Ok((rem, msg)) => { + let _pdu = Frame::new( + flow, + &stream_slice, + input, + current.len() as i64, + MQTTFrameType::Pdu as u8, + ); + SCLogDebug!("request msg {:?}", msg); + if let MQTTOperation::TRUNCATED(ref trunc) = msg.op { + SCLogDebug!( + "found truncated with skipped {} current len {}", + trunc.skipped_length, + current.len() + ); + if trunc.skipped_length >= current.len() { + self.skip_request = trunc.skipped_length - current.len(); + self.handle_msg(msg, true); + return AppLayerResult::ok(); + } else { + consumed += trunc.skipped_length; + current = ¤t[trunc.skipped_length..]; + self.handle_msg(msg, true); + self.skip_request = 0; + continue; + } + } + + self.mqtt_hdr_and_data_frames(flow, &stream_slice, &msg); + self.handle_msg(msg, false); + consumed += current.len() - rem.len(); + current = rem; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!( + "incomplete request: consumed {} needed {} (input len {})", + consumed, + (current.len() + 1), + input.len() + ); + return AppLayerResult::incomplete(consumed as u32, (current.len() + 1) as u32); + } + Err(_) => { + self.set_event_notx(MQTTEvent::MalformedTraffic, false); + return AppLayerResult::err(); + } + } + } + + return AppLayerResult::ok(); + } + + fn parse_response(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + let mut current = input; + + if input.is_empty() { + return AppLayerResult::ok(); + } + + let mut consumed = 0; + SCLogDebug!( + "skip_response {} input len {}", + self.skip_response, + current.len() + ); + if self.skip_response > 0 { + if input.len() <= self.skip_response { + self.skip_response -= current.len(); + return AppLayerResult::ok(); + } else { + current = &input[self.skip_response..]; + SCLogDebug!( + "skip end reached, skipping {} :{:?}", + self.skip_request, + current + ); + consumed = self.skip_response; + self.skip_response = 0; + } + } + + while !current.is_empty() { + SCLogDebug!("response: handling {}", current.len()); + match parse_message(current, self.protocol_version, self.max_msg_len) { + Ok((rem, msg)) => { + let _pdu = Frame::new( + flow, + &stream_slice, + input, + input.len() as i64, + MQTTFrameType::Pdu as u8, + ); + + SCLogDebug!("response msg {:?}", msg); + if let MQTTOperation::TRUNCATED(ref trunc) = msg.op { + SCLogDebug!( + "found truncated with skipped {} current len {}", + trunc.skipped_length, + current.len() + ); + if trunc.skipped_length >= current.len() { + self.skip_response = trunc.skipped_length - current.len(); + self.handle_msg(msg, true); + SCLogDebug!("skip_response now {}", self.skip_response); + return AppLayerResult::ok(); + } else { + consumed += trunc.skipped_length; + current = ¤t[trunc.skipped_length..]; + self.handle_msg(msg, true); + self.skip_response = 0; + continue; + } + } + + self.mqtt_hdr_and_data_frames(flow, &stream_slice, &msg); + self.handle_msg(msg, true); + consumed += current.len() - rem.len(); + current = rem; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!( + "incomplete response: consumed {} needed {} (input len {})", + consumed, + (current.len() + 1), + input.len() + ); + return AppLayerResult::incomplete(consumed as u32, (current.len() + 1) as u32); + } + Err(_) => { + self.set_event_notx(MQTTEvent::MalformedTraffic, true); + return AppLayerResult::err(); + } + } + } + + return AppLayerResult::ok(); + } + + fn set_event(tx: &mut MQTTTransaction, event: MQTTEvent) { + tx.tx_data.set_event(event as u8); + } + + fn set_event_notx(&mut self, event: MQTTEvent, toclient: bool) { + let mut tx = MQTTTransaction::new_empty(if toclient { + Direction::ToClient + } else { + Direction::ToServer + }); + self.tx_id += 1; + tx.tx_id = self.tx_id; + if toclient { + tx.toclient = true; + } else { + tx.toserver = true; + } + tx.complete = true; + tx.tx_data.set_event(event as u8); + self.transactions.push_back(tx); + } + + fn mqtt_hdr_and_data_frames( + &mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &MQTTMessage, + ) { + let hdr = stream_slice.as_slice(); + //MQTT payload has a fixed header of 2 bytes + let _mqtt_hdr = Frame::new(flow, stream_slice, hdr, 2, MQTTFrameType::Header as u8); + SCLogDebug!("mqtt_hdr Frame {:?}", _mqtt_hdr); + let rem_length = input.header.remaining_length as usize; + let data = &hdr[2..rem_length + 2]; + let _mqtt_data = Frame::new( + flow, + stream_slice, + data, + rem_length as i64, + MQTTFrameType::Data as u8, + ); + SCLogDebug!("mqtt_data Frame {:?}", _mqtt_data); + } +} + +// C exports. + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_probing_parser( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + let buf = build_slice!(input, input_len as usize); + match parse_fixed_header(buf) { + Ok((_, hdr)) => { + // reject unassigned message type + if hdr.message_type == MQTTTypeCode::UNASSIGNED { + return ALPROTO_FAILED; + } + // with 2 being the highest valid QoS level + if hdr.qos_level > 2 { + return ALPROTO_FAILED; + } + return ALPROTO_MQTT; + } + Err(Err::Incomplete(_)) => ALPROTO_UNKNOWN, + Err(_) => ALPROTO_FAILED, + } +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = MQTTState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_mqtt_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut MQTTState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, MQTTState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_parse_request( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, MQTTState); + return state.parse_request(flow, stream_slice); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_parse_response( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, MQTTState); + return state.parse_response(flow, stream_slice); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, MQTTState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, MQTTState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_is_toclient( + tx: *const std::os::raw::c_void, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, MQTTTransaction); + if tx.toclient { + return 1; + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, MQTTTransaction); + match direction.into() { + Direction::ToServer => { + if tx.complete || tx.toclient { + return 1; + } + } + Direction::ToClient => { + if tx.complete || tx.toserver { + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_get_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, +) -> u32 { + let tx = cast_pointer!(tx, MQTTTransaction); + return tx.logged.get(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_tx_set_logged( + _state: *mut std::os::raw::c_void, tx: *mut std::os::raw::c_void, logged: u32, +) { + let tx = cast_pointer!(tx, MQTTTransaction); + tx.logged.set(logged); +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"mqtt\0"; + +export_tx_data_get!(rs_mqtt_get_tx_data, MQTTTransaction); +export_state_data_get!(rs_mqtt_get_state_data, MQTTState); + +#[no_mangle] +pub unsafe extern "C" fn rs_mqtt_register_parser(cfg_max_msg_len: u32) { + let default_port = CString::new("[1883]").unwrap(); + let max_msg_len = &mut MAX_MSG_LEN; + *max_msg_len = cfg_max_msg_len; + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_mqtt_probing_parser), + probe_tc: Some(rs_mqtt_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_mqtt_state_new, + state_free: rs_mqtt_state_free, + tx_free: rs_mqtt_state_tx_free, + parse_ts: rs_mqtt_parse_request, + parse_tc: rs_mqtt_parse_response, + get_tx_count: rs_mqtt_state_get_tx_count, + get_tx: rs_mqtt_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_mqtt_tx_get_alstate_progress, + get_eventinfo: Some(MQTTEvent::get_event_info), + get_eventinfo_byid: Some(MQTTEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(crate::applayer::state_get_tx_iterator::<MQTTState, MQTTTransaction>), + get_tx_data: rs_mqtt_get_tx_data, + get_state_data: rs_mqtt_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: Some(MQTTFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(MQTTFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_MQTT = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + if let Some(val) = conf_get("app-layer.protocols.mqtt.max-tx") { + if let Ok(v) = val.parse::<usize>() { + MQTT_MAX_TX = v; + } else { + SCLogError!("Invalid value for mqtt.max-tx"); + } + } + } else { + SCLogDebug!("Protocol detector and parser disabled for MQTT."); + } +} diff --git a/rust/src/mqtt/mqtt_message.rs b/rust/src/mqtt/mqtt_message.rs new file mode 100644 index 0000000..390fc9e --- /dev/null +++ b/rust/src/mqtt/mqtt_message.rs @@ -0,0 +1,205 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use crate::mqtt::mqtt_property::*; +use crate::mqtt::parser::*; +use std::fmt; + +#[derive(Debug)] +pub struct MQTTMessage { + pub header: FixedHeader, + pub op: MQTTOperation, +} + +#[derive(Debug)] +pub enum MQTTOperation { + UNASSIGNED, + CONNECT(MQTTConnectData), + CONNACK(MQTTConnackData), + PUBLISH(MQTTPublishData), + PUBACK(MQTTMessageIdOnly), + PUBREC(MQTTMessageIdOnly), + PUBREL(MQTTMessageIdOnly), + PUBCOMP(MQTTMessageIdOnly), + SUBSCRIBE(MQTTSubscribeData), + SUBACK(MQTTSubackData), + UNSUBSCRIBE(MQTTUnsubscribeData), + UNSUBACK(MQTTUnsubackData), + AUTH(MQTTAuthData), + PINGREQ, + PINGRESP, + DISCONNECT(MQTTDisconnectData), + // TRUNCATED is special, representing a message that was not parsed + // in its entirety due to size constraints. There is no equivalent in + // the MQTT specification. + TRUNCATED(MQTTTruncatedData), +} + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, FromPrimitive, Debug)] +pub enum MQTTTypeCode { + UNASSIGNED = 0, + CONNECT = 1, + CONNACK = 2, + PUBLISH = 3, + PUBACK = 4, + PUBREC = 5, + PUBREL = 6, + PUBCOMP = 7, + SUBSCRIBE = 8, + SUBACK = 9, + UNSUBSCRIBE = 10, + UNSUBACK = 11, + PINGREQ = 12, + PINGRESP = 13, + DISCONNECT = 14, + AUTH = 15, +} + +impl MQTTTypeCode { + pub fn to_lower_str(&self) -> String { + self.to_string().to_lowercase() + } +} + +impl fmt::Display for MQTTTypeCode { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::str::FromStr for MQTTTypeCode { + type Err = String; + fn from_str(s: &str) -> Result<Self, Self::Err> { + let su = s.to_uppercase(); + let su_slice: &str = &su; + match su_slice { + "CONNECT" => Ok(MQTTTypeCode::CONNECT), + "CONNACK" => Ok(MQTTTypeCode::CONNACK), + "PUBLISH" => Ok(MQTTTypeCode::PUBLISH), + "PUBACK" => Ok(MQTTTypeCode::PUBACK), + "PUBREC" => Ok(MQTTTypeCode::PUBREC), + "PUBREL" => Ok(MQTTTypeCode::PUBREL), + "PUBCOMP" => Ok(MQTTTypeCode::PUBCOMP), + "SUBSCRIBE" => Ok(MQTTTypeCode::SUBSCRIBE), + "SUBACK" => Ok(MQTTTypeCode::SUBACK), + "UNSUBSCRIBE" => Ok(MQTTTypeCode::UNSUBSCRIBE), + "UNSUBACK" => Ok(MQTTTypeCode::UNSUBACK), + "PINGREQ" => Ok(MQTTTypeCode::PINGREQ), + "PINGRESP" => Ok(MQTTTypeCode::PINGRESP), + "DISCONNECT" => Ok(MQTTTypeCode::DISCONNECT), + "AUTH" => Ok(MQTTTypeCode::AUTH), + _ => Err(format!("'{}' is not a valid value for MQTTTypeCode", s)), + } + } +} + +#[derive(Debug)] +pub struct MQTTConnectData { + pub protocol_string: String, + pub protocol_version: u8, + pub username_flag: bool, + pub password_flag: bool, + pub will_retain: bool, + pub will_qos: u8, + pub will_flag: bool, + pub clean_session: bool, + pub keepalive: u16, + pub client_id: String, + pub will_topic: Option<String>, + pub will_message: Option<Vec<u8>>, + pub username: Option<String>, + pub password: Option<Vec<u8>>, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 + pub will_properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTConnackData { + pub return_code: u8, + pub session_present: bool, // MQTT 3.1.1 + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTPublishData { + pub topic: String, + pub message_id: Option<u16>, + pub message: Vec<u8>, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTMessageIdOnly { + pub message_id: u16, + pub reason_code: Option<u8>, // MQTT 5.0 + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTSubscribeTopicData { + pub topic_name: String, + pub qos: u8, +} + +#[derive(Debug)] +pub struct MQTTSubscribeData { + pub message_id: u16, + pub topics: Vec<MQTTSubscribeTopicData>, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTSubackData { + pub message_id: u16, + pub qoss: Vec<u8>, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTUnsubscribeData { + pub message_id: u16, + pub topics: Vec<String>, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTUnsubackData { + pub message_id: u16, + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 + pub reason_codes: Option<Vec<u8>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTAuthData { + pub reason_code: u8, // MQTT 5.0 + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTDisconnectData { + pub reason_code: Option<u8>, // MQTT 5.0 + pub properties: Option<Vec<MQTTProperty>>, // MQTT 5.0 +} + +#[derive(Debug)] +pub struct MQTTTruncatedData { + pub original_message_type: MQTTTypeCode, + pub skipped_length: usize, +} diff --git a/rust/src/mqtt/mqtt_property.rs b/rust/src/mqtt/mqtt_property.rs new file mode 100644 index 0000000..a716c4b --- /dev/null +++ b/rust/src/mqtt/mqtt_property.rs @@ -0,0 +1,269 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::mqtt::parser::*; +use nom7::number::streaming::*; +use nom7::IResult; + +// TODO: It might be useful to also add detection on property presence and +// content, e.g. mqtt.property: AUTHENTICATION_METHOD. +#[derive(Debug, PartialEq, PartialOrd)] +#[allow(non_camel_case_types)] +pub enum MQTTProperty { + UNKNOWN, + PAYLOAD_FORMAT_INDICATOR(u8), + MESSAGE_EXPIRY_INTERVAL(u32), + CONTENT_TYPE(String), + RESPONSE_TOPIC(String), + CORRELATION_DATA(Vec<u8>), + SUBSCRIPTION_IDENTIFIER(u32), + SESSION_EXPIRY_INTERVAL(u32), + ASSIGNED_CLIENT_IDENTIFIER(String), + SERVER_KEEP_ALIVE(u16), + AUTHENTICATION_METHOD(String), + AUTHENTICATION_DATA(Vec<u8>), + REQUEST_PROBLEM_INFORMATION(u8), + WILL_DELAY_INTERVAL(u32), + REQUEST_RESPONSE_INFORMATION(u8), + RESPONSE_INFORMATION(String), + SERVER_REFERENCE(String), + REASON_STRING(String), + RECEIVE_MAXIMUM(u16), + TOPIC_ALIAS_MAXIMUM(u16), + TOPIC_ALIAS(u16), + MAXIMUM_QOS(u8), + RETAIN_AVAILABLE(u8), + USER_PROPERTY((String, String)), + MAXIMUM_PACKET_SIZE(u32), + WILDCARD_SUBSCRIPTION_AVAILABLE(u8), + SUBSCRIPTION_IDENTIFIER_AVAILABLE(u8), + SHARED_SUBSCRIPTION_AVAILABLE(u8), +} + +impl crate::mqtt::mqtt_property::MQTTProperty { + pub fn set_json(&self, js: &mut JsonBuilder) -> Result<(), JsonError> { + match self { + crate::mqtt::mqtt_property::MQTTProperty::PAYLOAD_FORMAT_INDICATOR(v) => { + js.set_uint("payload_format_indicator", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::MESSAGE_EXPIRY_INTERVAL(v) => { + js.set_uint("message_expiry_interval", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::CONTENT_TYPE(v) => { + js.set_string("content_type", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::RESPONSE_TOPIC(v) => { + js.set_string("response_topic", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::CORRELATION_DATA(v) => { + js.set_string_from_bytes("correlation_data", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SUBSCRIPTION_IDENTIFIER(v) => { + js.set_uint("subscription_identifier", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SESSION_EXPIRY_INTERVAL(v) => { + js.set_uint("session_expiry_interval", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(v) => { + js.set_string("assigned_client_identifier", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SERVER_KEEP_ALIVE(v) => { + js.set_uint("server_keep_alive", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::AUTHENTICATION_METHOD(v) => { + js.set_string("authentication_method", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::AUTHENTICATION_DATA(v) => { + js.set_string_from_bytes("authentication_data", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::REQUEST_PROBLEM_INFORMATION(v) => { + js.set_uint("request_problem_information", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::WILL_DELAY_INTERVAL(v) => { + js.set_uint("will_delay_interval", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::REQUEST_RESPONSE_INFORMATION(v) => { + js.set_uint("request_response_information", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::RESPONSE_INFORMATION(v) => { + js.set_string("response_information", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SERVER_REFERENCE(v) => { + js.set_string("server_reference", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::REASON_STRING(v) => { + js.set_string("reason_string", v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::RECEIVE_MAXIMUM(v) => { + js.set_uint("receive_maximum", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::TOPIC_ALIAS_MAXIMUM(v) => { + js.set_uint("topic_alias_maximum", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::TOPIC_ALIAS(v) => { + js.set_uint("topic_alias", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::MAXIMUM_QOS(v) => { + js.set_uint("maximum_qos", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::RETAIN_AVAILABLE(v) => { + js.set_uint("retain_available", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::USER_PROPERTY((k, v)) => { + js.set_string(k, v)?; + } + crate::mqtt::mqtt_property::MQTTProperty::MAXIMUM_PACKET_SIZE(v) => { + js.set_uint("maximum_packet_size", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::WILDCARD_SUBSCRIPTION_AVAILABLE(v) => { + js.set_uint("wildcard_subscription_available", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SUBSCRIPTION_IDENTIFIER_AVAILABLE(v) => { + js.set_uint("subscription_identifier_available", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::SHARED_SUBSCRIPTION_AVAILABLE(v) => { + js.set_uint("shared_subscription_available", *v as u64)?; + } + crate::mqtt::mqtt_property::MQTTProperty::UNKNOWN => { + // pass + } + } + Ok(()) + } +} + +#[inline] +pub fn parse_qualified_property(input: &[u8], identifier: u32) -> IResult<&[u8], MQTTProperty> { + match identifier { + 1 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::PAYLOAD_FORMAT_INDICATOR(val))), + Err(e) => return Err(e), + }, + 2 => match be_u32(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::MESSAGE_EXPIRY_INTERVAL(val))), + Err(e) => return Err(e), + }, + 3 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::CONTENT_TYPE(val))), + Err(e) => return Err(e), + }, + 8 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::RESPONSE_TOPIC(val))), + Err(e) => return Err(e), + }, + 9 => match parse_mqtt_binary_data(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::CORRELATION_DATA(val))), + Err(e) => return Err(e), + }, + 11 => match parse_mqtt_variable_integer(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::SUBSCRIPTION_IDENTIFIER(val))), + Err(e) => return Err(e), + }, + 17 => match be_u32(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::SESSION_EXPIRY_INTERVAL(val))), + Err(e) => return Err(e), + }, + 18 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(val))), + Err(e) => return Err(e), + }, + 19 => match be_u16(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::SERVER_KEEP_ALIVE(val))), + Err(e) => return Err(e), + }, + 21 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::AUTHENTICATION_METHOD(val))), + Err(e) => return Err(e), + }, + 22 => match parse_mqtt_binary_data(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::AUTHENTICATION_DATA(val))), + Err(e) => return Err(e), + }, + 23 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::REQUEST_PROBLEM_INFORMATION(val))), + Err(e) => return Err(e), + }, + 24 => match be_u32(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::WILL_DELAY_INTERVAL(val))), + Err(e) => return Err(e), + }, + 25 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::REQUEST_RESPONSE_INFORMATION(val))), + Err(e) => return Err(e), + }, + 26 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::RESPONSE_INFORMATION(val))), + Err(e) => return Err(e), + }, + 28 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::SERVER_REFERENCE(val))), + Err(e) => return Err(e), + }, + 31 => match parse_mqtt_string(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::REASON_STRING(val))), + Err(e) => return Err(e), + }, + 33 => match be_u16(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::RECEIVE_MAXIMUM(val))), + Err(e) => return Err(e), + }, + 34 => match be_u16(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::TOPIC_ALIAS_MAXIMUM(val))), + Err(e) => return Err(e), + }, + 35 => match be_u16(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::TOPIC_ALIAS(val))), + Err(e) => return Err(e), + }, + 36 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::MAXIMUM_QOS(val))), + Err(e) => return Err(e), + }, + 37 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::RETAIN_AVAILABLE(val))), + Err(e) => return Err(e), + }, + 38 => match parse_mqtt_string_pair(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::USER_PROPERTY(val))), + Err(e) => return Err(e), + }, + 39 => match be_u32(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::MAXIMUM_PACKET_SIZE(val))), + Err(e) => return Err(e), + }, + 40 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::WILDCARD_SUBSCRIPTION_AVAILABLE(val))), + Err(e) => return Err(e), + }, + 41 => match be_u8(input) { + Ok((rem, val)) => { + return Ok((rem, MQTTProperty::SUBSCRIPTION_IDENTIFIER_AVAILABLE(val))) + } + Err(e) => return Err(e), + }, + 42 => match be_u8(input) { + Ok((rem, val)) => return Ok((rem, MQTTProperty::SHARED_SUBSCRIPTION_AVAILABLE(val))), + Err(e) => return Err(e), + }, + _ => { + return Ok((input, MQTTProperty::UNKNOWN)); + } + } +} diff --git a/rust/src/mqtt/parser.rs b/rust/src/mqtt/parser.rs new file mode 100644 index 0000000..8b1c8c5 --- /dev/null +++ b/rust/src/mqtt/parser.rs @@ -0,0 +1,1135 @@ +/* Copyright (C) 2020-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Sascha Steinbiss <sascha@steinbiss.name> + +use crate::common::nom7::bits; +use crate::mqtt::mqtt_message::*; +use crate::mqtt::mqtt_property::*; +use nom7::bits::streaming::take as take_bits; +use nom7::bytes::complete::take; +use nom7::bytes::streaming::take_while_m_n; +use nom7::combinator::{complete, cond, verify}; +use nom7::multi::{length_data, many0, many1}; +use nom7::number::streaming::*; +use nom7::sequence::tuple; +use nom7::{Err, IResult, Needed}; +use num_traits::FromPrimitive; + +#[derive(Copy, Clone, Debug)] +pub struct FixedHeader { + pub message_type: MQTTTypeCode, + pub dup_flag: bool, + pub qos_level: u8, + pub retain: bool, + pub remaining_length: u32, +} + +// PARSING HELPERS + +#[inline] +fn is_continuation_bit_set(b: u8) -> bool { + return (b & 128) != 0; +} + +#[inline] +fn convert_varint(continued: Vec<u8>, last: u8) -> u32 { + let mut multiplier = 1u32; + let mut value = 0u32; + for val in &continued { + value += (val & 127) as u32 * multiplier; + multiplier *= 128u32; + } + value += (last & 127) as u32 * multiplier; + return value; +} + +// DATA TYPES + +#[inline] +pub fn parse_mqtt_string(i: &[u8]) -> IResult<&[u8], String> { + let (i, content) = length_data(be_u16)(i)?; + Ok((i, String::from_utf8_lossy(content).to_string())) +} + +#[inline] +pub fn parse_mqtt_variable_integer(i: &[u8]) -> IResult<&[u8], u32> { + let (i, continued_part) = take_while_m_n(0, 3, is_continuation_bit_set)(i)?; + let (i, non_continued_part) = verify(be_u8, |&val| !is_continuation_bit_set(val))(i)?; + Ok(( + i, + convert_varint(continued_part.to_vec(), non_continued_part), + )) +} + +#[inline] +pub fn parse_mqtt_binary_data(i: &[u8]) -> IResult<&[u8], Vec<u8>> { + let (i, data) = length_data(be_u16)(i)?; + Ok((i, data.to_vec())) +} + +#[inline] +pub fn parse_mqtt_string_pair(i: &[u8]) -> IResult<&[u8], (String, String)> { + let (i, name) = parse_mqtt_string(i)?; + let (i, value) = parse_mqtt_string(i)?; + Ok((i, (name, value))) +} + +// MESSAGE COMPONENTS + +#[inline] +fn parse_property(i: &[u8]) -> IResult<&[u8], MQTTProperty> { + let (i, identifier) = parse_mqtt_variable_integer(i)?; + let (i, value) = parse_qualified_property(i, identifier)?; + Ok((i, value)) +} + +#[inline] +fn parse_properties(input: &[u8], precond: bool) -> IResult<&[u8], Option<Vec<MQTTProperty>>> { + // do not try to parse anything when precondition is not met + if !precond { + return Ok((input, None)); + } + // parse properties length + match parse_mqtt_variable_integer(input) { + Ok((rem, proplen)) => { + if proplen == 0 { + // no properties + return Ok((rem, None)); + } + // parse properties + let mut props = Vec::<MQTTProperty>::new(); + let (rem, mut newrem) = take(proplen as usize)(rem)?; + while !newrem.is_empty() { + match parse_property(newrem) { + Ok((rem2, val)) => { + props.push(val); + newrem = rem2; + } + Err(e) => return Err(e), + } + } + return Ok((rem, Some(props))); + } + Err(e) => return Err(e), + } +} + +#[inline] +fn parse_fixed_header_flags(i: &[u8]) -> IResult<&[u8], (u8, u8, u8, u8)> { + bits(tuple(( + take_bits(4u8), + take_bits(1u8), + take_bits(2u8), + take_bits(1u8), + )))(i) +} + +#[inline] +fn parse_message_type(code: u8) -> MQTTTypeCode { + match code { + 0..=15 => { + if let Some(t) = FromPrimitive::from_u8(code) { + return t; + } else { + return MQTTTypeCode::UNASSIGNED; + } + } + _ => { + // unreachable state in parser: we only pass values parsed from take_bits!(4u8) + debug_validate_fail!("can't have message codes >15 from 4 bits"); + MQTTTypeCode::UNASSIGNED + } + } +} + +#[inline] +pub fn parse_fixed_header(i: &[u8]) -> IResult<&[u8], FixedHeader> { + let (i, flags) = parse_fixed_header_flags(i)?; + let (i, remaining_length) = parse_mqtt_variable_integer(i)?; + Ok(( + i, + FixedHeader { + message_type: parse_message_type(flags.0), + dup_flag: flags.1 != 0, + qos_level: flags.2, + retain: flags.3 != 0, + remaining_length, + }, + )) +} + +#[inline] +#[allow(clippy::type_complexity)] +fn parse_connect_variable_flags(i: &[u8]) -> IResult<&[u8], (u8, u8, u8, u8, u8, u8, u8)> { + bits(tuple(( + take_bits(1u8), + take_bits(1u8), + take_bits(1u8), + take_bits(2u8), + take_bits(1u8), + take_bits(1u8), + take_bits(1u8), + )))(i) +} + +#[inline] +fn parse_connect(i: &[u8]) -> IResult<&[u8], MQTTConnectData> { + let (i, protocol_string) = parse_mqtt_string(i)?; + let (i, protocol_version) = be_u8(i)?; + let (i, flags) = parse_connect_variable_flags(i)?; + let (i, keepalive) = be_u16(i)?; + let (i, properties) = parse_properties(i, protocol_version == 5)?; + let (i, client_id) = parse_mqtt_string(i)?; + let (i, will_properties) = parse_properties(i, protocol_version == 5 && flags.4 != 0)?; + let (i, will_topic) = cond(flags.4 != 0, parse_mqtt_string)(i)?; + let (i, will_message) = cond(flags.4 != 0, parse_mqtt_binary_data)(i)?; + let (i, username) = cond(flags.0 != 0, parse_mqtt_string)(i)?; + let (i, password) = cond(flags.1 != 0, parse_mqtt_binary_data)(i)?; + Ok(( + i, + MQTTConnectData { + protocol_string, + protocol_version, + username_flag: flags.0 != 0, + password_flag: flags.1 != 0, + will_retain: flags.2 != 0, + will_qos: flags.3, + will_flag: flags.4 != 0, + clean_session: flags.5 != 0, + keepalive, + client_id, + will_topic, + will_message, + username, + password, + properties, + will_properties, + }, + )) +} + +#[inline] +fn parse_connack(protocol_version: u8) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTConnackData> { + move |i: &[u8]| { + let (i, topic_name_compression_response) = be_u8(i)?; + let (i, return_code) = be_u8(i)?; + let (i, properties) = parse_properties(i, protocol_version == 5)?; + Ok(( + i, + MQTTConnackData { + session_present: (topic_name_compression_response & 1) != 0, + return_code, + properties, + }, + )) + } +} + +#[inline] +fn parse_publish( + protocol_version: u8, + has_id: bool, +) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTPublishData> { + move |i: &[u8]| { + let (i, topic) = parse_mqtt_string(i)?; + let (i, message_id) = cond(has_id, be_u16)(i)?; + let (message, properties) = parse_properties(i, protocol_version == 5)?; + Ok(( + i, + MQTTPublishData { + topic, + message_id, + message: message.to_vec(), + properties, + }, + )) + } +} + +#[inline] +fn parse_msgidonly(protocol_version: u8) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTMessageIdOnly> { + move |input: &[u8]| { + if protocol_version < 5 { + // before v5 we don't even have to care about reason codes + // and properties, lucky us + return parse_msgidonly_v3(input); + } + let remaining_len = input.len(); + match be_u16(input) { + Ok((rem, message_id)) => { + if remaining_len == 2 { + // from the spec: " The Reason Code and Property Length can be + // omitted if the Reason Code is 0x00 (Success) and there are + // no Properties. In this case the message has a Remaining + // Length of 2." + return Ok(( + rem, + MQTTMessageIdOnly { + message_id, + reason_code: Some(0), + properties: None, + }, + )); + } + match be_u8(rem) { + Ok((rem, reason_code)) => { + // We are checking for 3 because in that case we have a + // header plus reason code, but no properties. + if remaining_len == 3 { + // no properties + return Ok(( + rem, + MQTTMessageIdOnly { + message_id, + reason_code: Some(reason_code), + properties: None, + }, + )); + } + match parse_properties(rem, true) { + Ok((rem, properties)) => { + return Ok(( + rem, + MQTTMessageIdOnly { + message_id, + reason_code: Some(reason_code), + properties, + }, + )); + } + Err(e) => return Err(e), + } + } + Err(e) => return Err(e), + } + } + Err(e) => return Err(e), + } + } +} + +#[inline] +fn parse_msgidonly_v3(i: &[u8]) -> IResult<&[u8], MQTTMessageIdOnly> { + let (i, message_id) = be_u16(i)?; + Ok(( + i, + MQTTMessageIdOnly { + message_id, + reason_code: None, + properties: None, + }, + )) +} + +#[inline] +fn parse_subscribe_topic(i: &[u8]) -> IResult<&[u8], MQTTSubscribeTopicData> { + let (i, topic_name) = parse_mqtt_string(i)?; + let (i, qos) = be_u8(i)?; + Ok((i, MQTTSubscribeTopicData { topic_name, qos })) +} + +#[inline] +fn parse_subscribe(protocol_version: u8) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTSubscribeData> { + move |i: &[u8]| { + let (i, message_id) = be_u16(i)?; + let (i, properties) = parse_properties(i, protocol_version == 5)?; + let (i, topics) = many1(complete(parse_subscribe_topic))(i)?; + Ok(( + i, + MQTTSubscribeData { + message_id, + topics, + properties, + }, + )) + } +} + +#[inline] +fn parse_suback(protocol_version: u8) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTSubackData> { + move |i: &[u8]| { + let (i, message_id) = be_u16(i)?; + let (qoss, properties) = parse_properties(i, protocol_version == 5)?; + Ok(( + i, + MQTTSubackData { + message_id, + qoss: qoss.to_vec(), + properties, + }, + )) + } +} + +#[inline] +fn parse_unsubscribe( + protocol_version: u8, +) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTUnsubscribeData> { + move |i: &[u8]| { + let (i, message_id) = be_u16(i)?; + let (i, properties) = parse_properties(i, protocol_version == 5)?; + let (i, topics) = many0(complete(parse_mqtt_string))(i)?; + Ok(( + i, + MQTTUnsubscribeData { + message_id, + topics, + properties, + }, + )) + } +} + +#[inline] +fn parse_unsuback(protocol_version: u8) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTUnsubackData> { + move |i: &[u8]| { + let (i, message_id) = be_u16(i)?; + let (i, properties) = parse_properties(i, protocol_version == 5)?; + let (i, reason_codes) = many0(complete(be_u8))(i)?; + Ok(( + i, + MQTTUnsubackData { + message_id, + properties, + reason_codes: Some(reason_codes), + }, + )) + } +} + +#[inline] +fn parse_disconnect( + remaining_len: usize, + protocol_version: u8, +) -> impl Fn(&[u8]) -> IResult<&[u8], MQTTDisconnectData> { + move |input: &[u8]| { + if protocol_version < 5 { + return Ok(( + input, + MQTTDisconnectData { + reason_code: None, + properties: None, + }, + )); + } + if remaining_len == 0 { + // The Reason Code and Property Length can be omitted if the Reason + // Code is 0x00 (Normal disconnection) and there are no Properties. + // In this case the DISCONNECT has a Remaining Length of 0. + return Ok(( + input, + MQTTDisconnectData { + reason_code: Some(0), + properties: None, + }, + )); + } + match be_u8(input) { + Ok((rem, reason_code)) => { + // We are checking for 1 because in that case we have a + // header plus reason code, but no properties. + if remaining_len == 1 { + // no properties + return Ok(( + rem, + MQTTDisconnectData { + reason_code: Some(0), + properties: None, + }, + )); + } + match parse_properties(rem, true) { + Ok((rem, properties)) => { + return Ok(( + rem, + MQTTDisconnectData { + reason_code: Some(reason_code), + properties, + }, + )); + } + Err(e) => return Err(e), + } + } + Err(e) => return Err(e), + } + } +} + +#[inline] +fn parse_auth(i: &[u8]) -> IResult<&[u8], MQTTAuthData> { + let (i, reason_code) = be_u8(i)?; + let (i, properties) = parse_properties(i, true)?; + Ok(( + i, + MQTTAuthData { + reason_code, + properties, + }, + )) +} + +#[inline] +fn parse_remaining_message<'a>( + full: &'a [u8], + len: usize, + skiplen: usize, + header: FixedHeader, + message_type: MQTTTypeCode, + protocol_version: u8, +) -> impl Fn(&'a [u8]) -> IResult<&'a [u8], MQTTMessage> { + move |input: &'a [u8]| { + match message_type { + MQTTTypeCode::CONNECT => match parse_connect(input) { + Ok((_rem, conn)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::CONNECT(conn), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::CONNACK => match parse_connack(protocol_version)(input) { + Ok((_rem, connack)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::CONNACK(connack), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::PUBLISH => { + match parse_publish(protocol_version, header.qos_level > 0)(input) { + Ok((_rem, publish)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::PUBLISH(publish), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + } + } + MQTTTypeCode::PUBACK + | MQTTTypeCode::PUBREC + | MQTTTypeCode::PUBREL + | MQTTTypeCode::PUBCOMP => match parse_msgidonly(protocol_version)(input) { + Ok((_rem, msgidonly)) => { + let msg = MQTTMessage { + header, + op: match message_type { + MQTTTypeCode::PUBACK => MQTTOperation::PUBACK(msgidonly), + MQTTTypeCode::PUBREC => MQTTOperation::PUBREC(msgidonly), + MQTTTypeCode::PUBREL => MQTTOperation::PUBREL(msgidonly), + MQTTTypeCode::PUBCOMP => MQTTOperation::PUBCOMP(msgidonly), + _ => MQTTOperation::UNASSIGNED, + }, + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::SUBSCRIBE => match parse_subscribe(protocol_version)(input) { + Ok((_rem, subs)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::SUBSCRIBE(subs), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::SUBACK => match parse_suback(protocol_version)(input) { + Ok((_rem, suback)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::SUBACK(suback), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::UNSUBSCRIBE => match parse_unsubscribe(protocol_version)(input) { + Ok((_rem, unsub)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::UNSUBSCRIBE(unsub), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::UNSUBACK => match parse_unsuback(protocol_version)(input) { + Ok((_rem, unsuback)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::UNSUBACK(unsuback), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::PINGREQ | MQTTTypeCode::PINGRESP => { + let msg = MQTTMessage { + header, + op: match message_type { + MQTTTypeCode::PINGREQ => MQTTOperation::PINGREQ, + MQTTTypeCode::PINGRESP => MQTTOperation::PINGRESP, + _ => MQTTOperation::UNASSIGNED, + }, + }; + Ok((&full[skiplen + len..], msg)) + } + MQTTTypeCode::DISCONNECT => match parse_disconnect(len, protocol_version)(input) { + Ok((_rem, disco)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::DISCONNECT(disco), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + MQTTTypeCode::AUTH => match parse_auth(input) { + Ok((_rem, auth)) => { + let msg = MQTTMessage { + header, + op: MQTTOperation::AUTH(auth), + }; + Ok((&full[skiplen + len..], msg)) + } + Err(e) => Err(e), + }, + // Unassigned message type code. Unlikely to happen with + // regular traffic, might be an indication for broken or + // crafted MQTT traffic. + _ => { + let msg = MQTTMessage { + header, + op: MQTTOperation::UNASSIGNED, + }; + return Ok((&full[skiplen + len..], msg)); + } + } + } +} + +pub fn parse_message( + input: &[u8], + protocol_version: u8, + max_msg_size: usize, +) -> IResult<&[u8], MQTTMessage> { + // Parse the fixed header first. This is identical across versions and can + // be between 2 and 5 bytes long. + match parse_fixed_header(input) { + Ok((fullrem, header)) => { + let len = header.remaining_length as usize; + // This is the length of the fixed header that we need to skip + // before returning the remainder. It is the sum of the length + // of the flag byte (1) and the length of the message length + // varint. + let skiplen = input.len() - fullrem.len(); + let message_type = header.message_type; + + // If the remaining length (message length) exceeds the specified + // limit, we return a special truncation message type, containing + // no parsed metadata but just the skipped length and the message + // type. + if len > max_msg_size { + let msg = MQTTMessage { + header, + op: MQTTOperation::TRUNCATED(MQTTTruncatedData { + original_message_type: message_type, + skipped_length: len + skiplen, + }), + }; + // In this case we return the full input buffer, since this is + // what the skipped_length value also refers to: header _and_ + // remaining length. + return Ok((input, msg)); + } + + // We have not exceeded the maximum length limit, but still do not + // have enough data in the input buffer to handle the full + // message. Signal this by returning an Incomplete IResult value. + if fullrem.len() < len { + return Err(Err::Incomplete(Needed::new(len - fullrem.len()))); + } + + // Parse the contents of the buffer into a single message. + // We reslice the remainder into the portion that we are interested + // in, according to the length we just parsed. This helps with the + // complete() parsers, where we would otherwise need to keep track + // of the already parsed length. + let rem = &fullrem[..len]; + + // Parse remaining message in buffer. We use complete() to ensure + // we do not request additional content in case of incomplete + // parsing, but raise an error instead as we should have all the + // data asked for in the header. + return complete(parse_remaining_message( + input, + len, + skiplen, + header, + message_type, + protocol_version, + ))(rem); + } + Err(err) => { + return Err(err); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use nom7::error::ErrorKind; + + fn test_mqtt_parse_variable_fail(buf0: &[u8]) { + let r0 = parse_mqtt_variable_integer(buf0); + match r0 { + Ok((_, _)) => { + panic!("Result should not have been ok."); + } + Err(Err::Error(err)) => { + assert_eq!(err.code, ErrorKind::Verify); + } + _ => { + panic!("Result should be an error."); + } + } + } + + fn test_mqtt_parse_variable_check(buf0: &[u8], expected: u32) { + let r0 = parse_mqtt_variable_integer(buf0); + match r0 { + Ok((_, val)) => { + assert_eq!(val, expected); + } + Err(_) => { + panic!("Result should have been ok."); + } + } + } + + #[test] + fn test_mqtt_parse_variable_integer_largest_input() { + test_mqtt_parse_variable_fail(&[0xFF, 0xFF, 0xFF, 0xFF]); + } + + #[test] + fn test_mqtt_parse_variable_integer_boundary() { + test_mqtt_parse_variable_fail(&[0xFF, 0xFF, 0xFF, 0x80]); + } + + #[test] + fn test_mqtt_parse_variable_integer_largest_valid() { + test_mqtt_parse_variable_check(&[0xFF, 0xFF, 0xFF, 0x7F], 268435455); + } + + #[test] + fn test_mqtt_parse_variable_integer_smallest_valid() { + test_mqtt_parse_variable_check(&[0x0], 0); + } + + #[test] + fn test_parse_fixed_header() { + let buf = [ + 0x30, /* Header Flags: 0x30, Message Type: Publish Message, QoS Level: At most once delivery (Fire and Forget) */ + 0xb7, 0x97, 0x02, /* Msg Len: 35767 */ + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xa0, + ]; + + let result = parse_fixed_header(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message.message_type, MQTTTypeCode::PUBLISH); + assert!(!message.dup_flag); + assert_eq!(message.qos_level, 0); + assert!(!message.retain); + assert_eq!(message.remaining_length, 35767); + assert_eq!(remainder.len(), 17); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_properties() { + let buf = [ + 0x03, 0x21, 0x00, 0x14, /* Properties */ + 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0xa0, + ]; + + let result = parse_properties(&buf, true); + match result { + Ok((remainder, message)) => { + let res = message.unwrap(); + assert_eq!(res[0], MQTTProperty::RECEIVE_MAXIMUM(20)); + assert_eq!(remainder.len(), 17); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + #[test] + fn test_parse_connect() { + let buf = [ + 0x00, 0x04, /* Protocol Name Length: 4 */ + 0x4d, 0x51, 0x54, 0x54, /* Protocol Name: MQTT */ + 0x05, /* Version: MQTT v5.0 (5) */ + 0xc2, /*Connect Flags: 0xc2, User Name Flag, Password Flag, QoS Level: At most once delivery (Fire and Forget), Clean Session Flag */ + 0x00, 0x3c, /* Keep Alive: 60 */ + 0x03, 0x21, 0x00, 0x14, /* Properties */ + 0x00, 0x00, /* Client ID Length: 0 */ + 0x00, 0x04, /* User Name Length: 4 */ + 0x75, 0x73, 0x65, 0x72, /* User Name: user */ + 0x00, 0x04, /* Password Length: 4 */ + 0x71, 0x61, 0x71, 0x73, /* Password: pass */ + ]; + + let result = parse_connect(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message.protocol_string, "MQTT"); + assert_eq!(message.protocol_version, 5); + assert!(message.username_flag); + assert!(message.password_flag); + assert!(!message.will_retain); + assert_eq!(message.will_qos, 0); + assert!(!message.will_flag); + assert!(message.clean_session); + assert_eq!(message.keepalive, 60); + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_connack() { + let buf = [ + 0x00, /* Acknowledge Flags: 0x00 (0000 000. = Reserved: Not set )(.... ...0 = Session Present: Not set) */ + 0x00, /* Reason Code: Success (0) */ + 0x2f, /* Total Length: 47 */ + 0x22, /* ID: Topic Alias Maximum (0x22) */ + 0x00, 0x0a, /* Value: 10 */ + 0x12, /* ID: Assigned Client Identifier (0x12) */ + 0x00, 0x29, /* Length: 41 */ + 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x31, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30, 0x30, 0x2d, + 0x30, 0x38, 0x45, 0x33, 0x2d, 0x33, 0x42, 0x41, 0x31, 0x2d, 0x32, 0x45, 0x39, 0x37, + 0x2d, 0x45, 0x39, 0x41, 0x30, 0x42, 0x34, 0x30, 0x36, 0x34, 0x42, 0x46, + 0x35, /* 41 byte Value: auto-1B43E800-08E3-3BA1-2E97-E9A0B4064BF5 */ + ]; + let client_identifier = "auto-1B43E800-08E3-3BA1-2E97-E9A0B4064BF5"; + + let result = parse_connack(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + let props = message.properties.unwrap(); + assert_eq!(props[0], MQTTProperty::TOPIC_ALIAS_MAXIMUM(10)); + assert_eq!( + props[1], + MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(client_identifier.to_string()) + ); + assert_eq!(message.return_code, 0); + assert!(!message.session_present); + assert_eq!(remainder.len(), 0); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_publish() { + let buf = [ + 0x00, 06, /* Topic Length: 6 */ + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */ + 0x00, 0x01, /* Message Identifier: 1 */ + 0x00, /* Properties 6 */ + 0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30, + ]; + + let result = parse_publish(5, true); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + let message_id = message.message_id.unwrap(); + assert_eq!(message.topic, "topicX"); + assert_eq!(message_id, 1); + assert_eq!(remainder.len(), 13); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_msgidonly_v3() { + let buf = [ + 0x00, 01, /* Message Identifier: 1 */ + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, 0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, + 0x33, 0x45, 0x38, 0x30, + ]; + + let result = parse_msgidonly(3); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + assert_eq!(message.message_id, 1); + assert_eq!(remainder.len(), 18); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_msgidonly_v5() { + let buf = [ + 0x00, 01, /* Message Identifier: 1 */ + 0x00, /* Reason Code: 0 */ + 0x00, /* Properties */ + 0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30, + ]; + + let result = parse_msgidonly(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + let reason_code = message.reason_code.unwrap(); + assert_eq!(message.message_id, 1); + assert_eq!(reason_code, 0); + assert_eq!(remainder.len(), 12); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_subscribe() { + let buf = [ + 0x00, 0x01, /* Message Identifier: 1 */ + 0x00, /* Properties 6 */ + 0x00, 0x06, /* Topic Length: 6 */ + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */ + 0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */ + 0x00, 0x06, /* Topic Length: 6 */ + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x59, /* Topic: topicY */ + 0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */ + 0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30, + ]; + + let result = parse_subscribe(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + assert_eq!(message.topics[0].topic_name, "topicX"); + assert_eq!(message.topics[1].topic_name, "topicY"); + assert_eq!(message.topics[0].qos, 0); + assert_eq!(message.message_id, 1); + assert_eq!(remainder.len(), 12); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + #[test] + fn test_parse_suback() { + let buf = [ + 0x00, 0x01, /* Message Identifier: 1 */ + 0x00, /* Properties 6 */ + 0x00, 0x00, /* Topic Length: 6 */ + ]; + + let result = parse_suback(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + assert_eq!(message.qoss[0], 0); + assert_eq!(message.message_id, 1); + assert_eq!(remainder.len(), 3); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + #[test] + fn test_parse_unsubscribe() { + let buf = [ + 0x00, 0x01, /* Message Identifier: 1 */ + 0x00, /* Properties 6 */ + 0x00, 0x06, /* Topic Length: 6 */ + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */ + 0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */ + ]; + + let result = parse_unsubscribe(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + assert_eq!(message.topics[0], "topicX"); + assert_eq!(message.message_id, 1); + assert_eq!(remainder.len(), 1); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_unsuback() { + let buf = [ + 0x00, 0x01, /* Message Identifier: 1 */ + 0x00, /* Properties 6 */ + 0x00, /* Reason Code */ + ]; + + let result = parse_unsuback(5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + let reason_codes = message.reason_codes.unwrap(); + assert_eq!(reason_codes[0], 0); + assert_eq!(message.message_id, 1); + assert_eq!(remainder.len(), 0); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_disconnect() { + let buf = [ + 0xe0, /* Reason: 0 */ + 0x00, /* Message Identifier: 1 */ + ]; + + let result = parse_disconnect(0, 5); + let input = result(&buf); + match input { + Ok((remainder, message)) => { + let reason_code = message.reason_code.unwrap(); + assert_eq!(reason_code, 0); + assert_eq!(remainder.len(), 2); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_message() { + let buf = [ + 0x10, /* Message Identifier: 1 */ + 0x2f, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x05, + 0xc2, /* Connect Flags: 0xc2, User Name Flag, Password Flag, QoS Level: At most once delivery (Fire and Forget), Clean Session Flag */ + 0x00, 0x3c, 0x03, 0x21, 0x00, 0x14, /* Properties */ + 0x00, 0x13, 0x6d, 0x79, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x69, 0x73, 0x6d, 0x79, 0x70, + 0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x00, 0x04, 0x75, 0x73, 0x65, 0x72, 0x00, + 0x04, 0x70, 0x61, 0x73, 0x73, + ]; + + let result = parse_message(&buf, 5, 40); + match result { + Ok((remainder, message)) => { + assert_eq!(message.header.message_type, MQTTTypeCode::CONNECT); + assert!(!message.header.dup_flag); + assert_eq!(message.header.qos_level, 0); + assert!(!message.header.retain); + assert_eq!(remainder.len(), 49); + } + + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } +} diff --git a/rust/src/nfs/log.rs b/rust/src/nfs/log.rs new file mode 100644 index 0000000..f6fdc8f --- /dev/null +++ b/rust/src/nfs/log.rs @@ -0,0 +1,180 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::string::String; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::nfs::types::*; +use crate::nfs::nfs::*; +use crc::crc32; + +#[no_mangle] +pub extern "C" fn rs_nfs_tx_logging_is_filtered(state: &mut NFSState, + tx: &mut NFSTransaction) + -> u8 +{ + // TODO probably best to make this configurable + + if state.nfs_version <= 3 && tx.procedure == NFSPROC3_GETATTR { + return 1; + } + + return 0; +} + +fn nfs_rename_object(tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + let from_str = String::from_utf8_lossy(&tx.file_name); + js.set_string("from", &from_str)?; + + let to_vec = match tx.type_data { + Some(NFSTransactionTypeData::RENAME(ref x)) => { x.to_vec() }, + _ => { Vec::new() } + }; + + let to_str = String::from_utf8_lossy(&to_vec); + js.set_string("to", &to_str)?; + Ok(()) +} + +fn nfs_creds_object(tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + let mach_name = String::from_utf8_lossy(&tx.request_machine_name); + js.set_string("machine_name", &mach_name)?; + js.set_uint("uid", tx.request_uid as u64)?; + js.set_uint("gid", tx.request_gid as u64)?; + Ok(()) +} + +fn nfs_file_object(tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + js.set_bool("first", tx.is_first)?; + js.set_bool("last", tx.is_last)?; + + if let Some(NFSTransactionTypeData::FILE(ref tdf)) = tx.type_data { + js.set_uint("last_xid", tdf.file_last_xid as u64)?; + js.set_uint("chunks", tdf.chunk_count as u64)?; + } + Ok(()) +} +/* +fn nfs_handle2hex(bytes: &Vec<u8>) -> String { + let strings: Vec<String> = bytes.iter() + .map(|b| format!("{:02x}", b)) + .collect(); + strings.join("") +} +*/ +fn nfs_handle2crc(bytes: &[u8]) -> u32 { + let c = crc32::checksum_ieee(bytes); + c +} + +fn nfs_common_header(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + js.set_uint("version", state.nfs_version as u64)?; + let proc_string = if state.nfs_version < 4 { + nfs3_procedure_string(tx.procedure) + } else { + nfs4_procedure_string(tx.procedure) + }; + js.set_string("procedure", &proc_string)?; + let file_name = String::from_utf8_lossy(&tx.file_name); + js.set_string("filename", &file_name)?; + + if !tx.file_handle.is_empty() { + //js.set_string("handle", &nfs_handle2hex(&tx.file_handle)); + let c = nfs_handle2crc(&tx.file_handle); + let s = format!("{:x}", c); + js.set_string("hhash", &s)?; + } + js.set_uint("id", tx.id)?; + js.set_bool("file_tx", tx.is_file_tx)?; + Ok(()) +} + +fn nfs_log_request(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + nfs_common_header(state, tx, js)?; + js.set_string("type", "request")?; + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_nfs_log_json_request(state: &mut NFSState, tx: &mut NFSTransaction, + js: &mut JsonBuilder) -> bool +{ + nfs_log_request(state, tx, js).is_ok() +} + +fn nfs_log_response(state: &NFSState, tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + nfs_common_header(state, tx, js)?; + js.set_string("type", "response")?; + + js.set_string("status", &nfs3_status_string(tx.nfs_response_status))?; + + if state.nfs_version <= 3 { + if tx.procedure == NFSPROC3_READ { + js.open_object("read")?; + nfs_file_object(tx, js)?; + js.close()?; + } else if tx.procedure == NFSPROC3_WRITE { + js.open_object("write")?; + nfs_file_object(tx, js)?; + js.close()?; + } else if tx.procedure == NFSPROC3_RENAME { + js.open_object("rename")?; + nfs_rename_object(tx, js)?; + js.close()?; + } + } + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_nfs_log_json_response(state: &mut NFSState, tx: &mut NFSTransaction, + js: &mut JsonBuilder) -> bool +{ + nfs_log_response(state, tx, js).is_ok() +} + +fn rpc_log_response(tx: &NFSTransaction, js: &mut JsonBuilder) + -> Result<(), JsonError> +{ + js.set_uint("xid", tx.xid as u64)?; + js.set_string("status", &rpc_status_string(tx.rpc_response_status))?; + js.set_string("auth_type", &rpc_auth_type_string(tx.auth_type))?; + if tx.auth_type == RPCAUTH_UNIX { + js.open_object("creds")?; + nfs_creds_object(tx, js)?; + js.close()?; + } + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_rpc_log_json_response(tx: &mut NFSTransaction, + js: &mut JsonBuilder) -> bool +{ + rpc_log_response(tx, js).is_ok() +} diff --git a/rust/src/nfs/mod.rs b/rust/src/nfs/mod.rs new file mode 100644 index 0000000..2f6fe84 --- /dev/null +++ b/rust/src/nfs/mod.rs @@ -0,0 +1,33 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! NFS application layer, parser, logger module. + +pub mod types; +pub mod rpc_records; +pub mod nfs_records; +pub mod nfs2_records; +pub mod nfs3_records; +pub mod nfs4_records; +pub mod nfs; +pub mod nfs2; +pub mod nfs3; +pub mod nfs4; +pub mod log; + +//#[cfg(feature = "lua")] +//pub mod lua; diff --git a/rust/src/nfs/nfs.rs b/rust/src/nfs/nfs.rs new file mode 100644 index 0000000..dfb5e0e --- /dev/null +++ b/rust/src/nfs/nfs.rs @@ -0,0 +1,2121 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Victor Julien + +use std; +use std::cmp; +use std::collections::HashMap; +use std::ffi::CString; + +use nom7::{Err, Needed}; + +use crate::applayer; +use crate::applayer::*; +use crate::frames::*; +use crate::core::*; +use crate::conf::*; +use crate::filetracker::*; +use crate::filecontainer::*; + +use crate::nfs::types::*; +use crate::nfs::rpc_records::*; +use crate::nfs::nfs_records::*; +use crate::nfs::nfs2_records::*; +use crate::nfs::nfs3_records::*; + +pub static mut SURICATA_NFS_FILE_CONFIG: Option<&'static SuricataFileContext> = None; + +pub const NFS_MIN_FRAME_LEN: u16 = 32; + +static mut NFS_MAX_TX: usize = 1024; + +pub const RPC_TCP_PRE_CREDS: usize = 28; +pub const RPC_UDP_PRE_CREDS: usize = 24; + +static mut ALPROTO_NFS: AppProto = ALPROTO_UNKNOWN; +/* + * Record parsing. + * + * Incomplete records come in due to TCP splicing. For all record types + * except READ and WRITE, processing only begins when the full record + * is available. For READ/WRITE partial records are processed as well to + * avoid queuing too much data. + * + * Getting file names. + * + * NFS makes heavy use of 'file handles' for operations. In many cases it + * uses a file name just once and after that just the handle. For example, + * if a client did a file listing (e.g. READDIRPLUS) and would READ the + * file afterwards, the name will only appear in the READDIRPLUS answer. + * To be able to log the names we store a mapping between file handles + * and file names in NFSState::namemap. + * + * Mapping NFS to Suricata's transaction model. + * + * The easiest way to do transactions would be to map each command/reply with + * the same XID to a transaction. This would allow for per XID logging, detect + * etc. However this model doesn't fit well with file tracking. The file + * tracking in Suricata is really expecting to be one or more files to live + * inside a single transaction. Would XID pairs be a transaction however, + * there would be many transactions forming a single file. This will be very + * inefficient. + * + * The model implemented here is as follows: each file transfer is a single + * transaction. All XID pairs unrelated to those file transfers create + * transactions per pair. + * + * A complicating factor is that the procedure matching is per tx, and a + * file transfer may have multiple procedures involved. Currently now only + * a COMMIT after WRITEs. A vector of additional procedures is kept to + * match on this. + * + * File tracking + * + * Files are tracked per 'FileTransferTracker' and are stored in the + * NFSTransaction where they can be looked up per handle as part of the + * Transaction lookup. + */ + +#[derive(AppLayerFrameType)] +pub enum NFSFrameType { + RPCPdu, + RPCHdr, + RPCData, + RPCCreds, // for rpc calls | rpc.creds [creds_flavor + creds_len + creds] + + NFSPdu, + NFSStatus, + + NFS4Pdu, + NFS4Hdr, + NFS4Ops, + NFS4Status, +} + +#[derive(FromPrimitive, Debug, AppLayerEvent)] +pub enum NFSEvent { + MalformedData = 0, + NonExistingVersion = 1, + UnsupportedVersion = 2, + TooManyTransactions = 3, +} + +#[derive(Debug)] +pub enum NFSTransactionTypeData { + RENAME(Vec<u8>), + FILE(NFSTransactionFile), +} + +#[derive(Default, Debug)] +pub struct NFSTransactionFile { + /// file transactions are unidirectional in the sense that they track + /// a single file on one direction + pub direction: Direction, // Direction::ToClient or Direction::ToServer + + /// additional procedures part of a single file transfer. Currently + /// only COMMIT on WRITEs. + pub file_additional_procs: Vec<u32>, + + pub chunk_count: u32, + + /// last xid of this file transfer. Last READ or COMMIT normally. + pub file_last_xid: u32, + + /// after a gap, this will be set to a time in the future. If the file + /// receives no updates before that, it will be considered complete. + pub post_gap_ts: u64, + + /// file tracker for a single file. Boxed so that we don't use + /// as much space if we're not a file tx. + pub file_tracker: FileTransferTracker, +} + +impl NFSTransactionFile { + pub fn new() -> Self { + return Self { + file_tracker: FileTransferTracker::new(), + ..Default::default() + } + } + pub fn update_file_flags(&mut self, flow_file_flags: u16) { + let dir_flag = if self.direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; + self.file_tracker.file_flags = unsafe { FileFlowFlagsToFlags(flow_file_flags, dir_flag) }; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_gettxfiles(_state: *mut std::ffi::c_void, tx: *mut std::ffi::c_void, direction: u8) -> AppLayerGetFileState { + let tx = cast_pointer!(tx, NFSTransaction); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let tx_dir : u8 = tdf.direction.into(); + if direction & tx_dir != 0 { + if let Some(sfcm) = { SURICATA_NFS_FILE_CONFIG } { + return AppLayerGetFileState { fc: &mut tdf.file_tracker.file, cfg: sfcm.files_sbcfg } + } + } + } + AppLayerGetFileState::err() +} + +#[derive(Debug)] +pub struct NFSTransaction { + pub id: u64, /// internal id + pub xid: u32, /// nfs req/reply pair id + pub procedure: u32, + /// file name of the object we're dealing with. In case of RENAME + /// this is the 'from' or original name. + pub file_name: Vec<u8>, + + pub auth_type: u32, + pub request_machine_name: Vec<u8>, + pub request_uid: u32, + pub request_gid: u32, + + pub rpc_response_status: u32, + pub nfs_response_status: u32, + + pub is_first: bool, + pub is_last: bool, + + /// for state tracking. false means this side is in progress, true + /// that it's complete. + pub request_done: bool, + pub response_done: bool, + + pub nfs_version: u16, + + /// is a special file tx that we look up by file_handle instead of XID + pub is_file_tx: bool, + pub is_file_closed: bool, + pub file_handle: Vec<u8>, + + /// Procedure type specific data + /// TODO see if this can be an `Option<Box<NFSTransactionTypeData>>`. Initial + /// attempt failed. + pub type_data: Option<NFSTransactionTypeData>, + + pub tx_data: AppLayerTxData, +} + +impl Default for NFSTransaction { + fn default() -> Self { + Self::new() + } +} + +impl NFSTransaction { + pub fn new() -> Self { + return Self { + id: 0, + xid: 0, + procedure: 0, + file_name:Vec::new(), + request_machine_name:Vec::new(), + request_uid:0, + request_gid:0, + rpc_response_status:0, + nfs_response_status:0, + auth_type: 0, + is_first: false, + is_last: false, + request_done: false, + response_done: false, + nfs_version:0, + is_file_tx: false, + is_file_closed: false, + file_handle:Vec::new(), + type_data: None, + tx_data: AppLayerTxData::new(), + } + } + + pub fn free(&mut self) { + debug_validate_bug_on!(self.tx_data.files_opened > 1); + debug_validate_bug_on!(self.tx_data.files_logged > 1); + } +} + +impl Transaction for NFSTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl Drop for NFSTransaction { + fn drop(&mut self) { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = self.type_data { + if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { + tdf.file_tracker.file.free(sfcm); + } + } + self.free(); + } +} + +#[derive(Debug)] +pub struct NFSRequestXidMap { + pub progver: u32, + pub procedure: u32, + pub chunk_offset: u64, + pub file_name:Vec<u8>, + + /// READ replies can use this to get to the handle the request used + pub file_handle:Vec<u8>, + + pub gssapi_proc: u32, + pub gssapi_service: u32, +} + +impl NFSRequestXidMap { + pub fn new(progver: u32, procedure: u32, chunk_offset: u64) -> NFSRequestXidMap { + NFSRequestXidMap { + progver, + procedure, + chunk_offset, + file_name:Vec::new(), + file_handle:Vec::new(), + gssapi_proc: 0, + gssapi_service: 0, + } + } +} + +/// little wrapper around the FileTransferTracker::new_chunk method +pub fn filetracker_newchunk(ft: &mut FileTransferTracker, name: &[u8], data: &[u8], + chunk_offset: u64, chunk_size: u32, fill_bytes: u8, is_last: bool, xid: &u32) +{ + if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { + ft.new_chunk(sfcm, name, data, chunk_offset, + chunk_size, fill_bytes, is_last, xid); + } +} + +fn filetracker_trunc(ft: &mut FileTransferTracker) +{ + if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { + ft.trunc(sfcm); + } +} + +pub fn filetracker_close(ft: &mut FileTransferTracker) +{ + if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { + ft.close(sfcm); + } +} + +fn filetracker_update(ft: &mut FileTransferTracker, data: &[u8], gap_size: u32) -> u32 +{ + if let Some(sfcm) = unsafe { SURICATA_NFS_FILE_CONFIG } { + ft.update(sfcm, data, gap_size) + } else { + 0 + } +} + + +#[derive(Debug)] +pub struct NFSState { + state_data: AppLayerStateData, + + /// map xid to procedure so replies can lookup the procedure + pub requestmap: HashMap<u32, NFSRequestXidMap>, + + /// map file handle (1) to name (2) + pub namemap: HashMap<Vec<u8>, Vec<u8>>, + + /// transactions list + pub transactions: Vec<NFSTransaction>, + + /// partial record tracking + pub ts_chunk_xid: u32, + pub tc_chunk_xid: u32, + /// size of the current chunk that we still need to receive + pub ts_chunk_left: u32, + pub tc_chunk_left: u32, + /// file handle of in progress toserver WRITE file chunk + ts_chunk_fh: Vec<u8>, + + ts_ssn_gap: bool, + tc_ssn_gap: bool, + + ts_gap: bool, // last TS update was gap + tc_gap: bool, // last TC update was gap + + is_udp: bool, + + /// true as long as we have file txs that are in a post-gap + /// state. It means we'll do extra house keeping for those. + check_post_gap_file_txs: bool, + post_gap_files_checked: bool, + + pub nfs_version: u16, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, + + /// Timestamp in seconds of last update. This is packet time, + /// potentially coming from pcaps. + ts: u64, +} + +impl Default for NFSState { + fn default() -> Self { + Self::new() + } +} + +impl State<NFSTransaction> for NFSState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&NFSTransaction> { + self.transactions.get(index) + } +} + +impl NFSState { + /// Allocation function for a new TLS parser instance + pub fn new() -> NFSState { + NFSState { + state_data: AppLayerStateData::new(), + requestmap:HashMap::new(), + namemap:HashMap::new(), + transactions: Vec::new(), + ts_chunk_xid:0, + tc_chunk_xid:0, + ts_chunk_left:0, + tc_chunk_left:0, + ts_chunk_fh:Vec::new(), + ts_ssn_gap:false, + tc_ssn_gap:false, + ts_gap:false, + tc_gap:false, + is_udp:false, + check_post_gap_file_txs:false, + post_gap_files_checked:false, + nfs_version:0, + tx_id:0, + ts: 0, + } + } + + fn update_ts(&mut self, ts: u64) { + if ts != self.ts { + self.ts = ts; + self.post_gap_files_checked = false; + } + } + + pub fn new_tx(&mut self) -> NFSTransaction { + let mut tx = NFSTransaction::new(); + self.tx_id += 1; + tx.id = self.tx_id; + if self.transactions.len() > unsafe { NFS_MAX_TX } { + // set at least one another transaction to the drop state + for tx_old in &mut self.transactions { + if !tx_old.request_done || !tx_old.response_done { + tx_old.request_done = true; + tx_old.response_done = true; + tx_old.is_file_closed = true; + tx_old.tx_data.set_event(NFSEvent::TooManyTransactions as u8); + break; + } + } + } + return tx; + } + + pub fn free_tx(&mut self, tx_id: u64) { + //SCLogNotice!("Freeing TX with ID {}", tx_id); + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for i in 0..len { + let tx = &self.transactions[i]; + if tx.id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + SCLogDebug!("freeing TX with ID {} at index {}", tx_id, index); + self.transactions.remove(index); + } + } + + pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NFSTransaction> { + SCLogDebug!("get_tx_by_id: tx_id={}", tx_id); + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + SCLogDebug!("Found NFS TX with ID {}", tx_id); + return Some(tx); + } + } + SCLogDebug!("Failed to find NFS TX with ID {}", tx_id); + return None; + } + + pub fn get_tx_by_xid(&mut self, tx_xid: u32) -> Option<&mut NFSTransaction> { + SCLogDebug!("get_tx_by_xid: tx_xid={}", tx_xid); + for tx in &mut self.transactions { + if !tx.is_file_tx && tx.xid == tx_xid { + SCLogDebug!("Found NFS TX with ID {} XID {:04X}", tx.id, tx.xid); + return Some(tx); + } + } + SCLogDebug!("Failed to find NFS TX with XID {:04X}", tx_xid); + return None; + } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: NFSEvent) { + let len = self.transactions.len(); + if len == 0 { + return; + } + + let tx = &mut self.transactions[len - 1]; + tx.tx_data.set_event(event as u8); + } + + // TODO maybe not enough users to justify a func + pub fn mark_response_tx_done(&mut self, xid: u32, rpc_status: u32, nfs_status: u32, resp_handle: &Vec<u8>) + { + if let Some(mytx) = self.get_tx_by_xid(xid) { + mytx.response_done = true; + mytx.rpc_response_status = rpc_status; + mytx.nfs_response_status = nfs_status; + if mytx.file_handle.is_empty() && !resp_handle.is_empty() { + mytx.file_handle = resp_handle.to_vec(); + } + + SCLogDebug!("process_reply_record: tx ID {} XID {:04X} REQUEST {} RESPONSE {}", + mytx.id, mytx.xid, mytx.request_done, mytx.response_done); + } else { + //SCLogNotice!("process_reply_record: not TX found for XID {}", r.hdr.xid); + } + } + + fn add_rpc_udp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { + let rpc_udp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); + SCLogDebug!("rpc_udp_pdu ts frame {:?}", rpc_udp_ts_pdu); + rpc_udp_ts_pdu + } + + fn add_rpc_udp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) { + let _rpc_udp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8); + SCLogDebug!("rpc_creds ts frame {:?}", _rpc_udp_ts_creds); + } + + fn add_rpc_tcp_ts_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { + let rpc_tcp_ts_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); + SCLogDebug!("rpc_tcp_pdu ts frame {:?}", rpc_tcp_ts_pdu); + rpc_tcp_ts_pdu + } + + fn add_rpc_tcp_ts_creds(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], creds_len: i64) { + let _rpc_tcp_ts_creds = Frame::new(flow, stream_slice, input, creds_len, NFSFrameType::RPCCreds as u8); + SCLogDebug!("rpc_tcp_ts_creds {:?}", _rpc_tcp_ts_creds); + } + + fn add_nfs_ts_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) { + let _nfs_req_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8); + SCLogDebug!("nfs_ts_pdu Frame {:?}", _nfs_req_pdu); + } + + fn add_nfs4_ts_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) { + let _nfs4_ts_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8); + SCLogDebug!("nfs4_ts_pdu Frame: {:?}", _nfs4_ts_pdu); + if nfs4_len > 8 { + let _nfs4_ts_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8); + SCLogDebug!("nfs4_ts_hdr Frame {:?}", _nfs4_ts_hdr); + let _nfs4_ts_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8); + SCLogDebug!("nfs4_ts_ops Frame {:?}", _nfs4_ts_ops); + } + } + + fn add_rpc_udp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) -> Option<Frame> { + let rpc_udp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_len, NFSFrameType::RPCPdu as u8); + SCLogDebug!("rpc_tc_pdu frame {:?}", rpc_udp_tc_pdu); + rpc_udp_tc_pdu + } + + fn add_rpc_udp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_len: i64) { + if rpc_len > 8 { + let _rpc_udp_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::RPCHdr as u8); + let _rpc_udp_tc_data = Frame::new(flow, stream_slice, &input[8..], rpc_len - 8, NFSFrameType::RPCData as u8); + SCLogDebug!("rpc_udp_tc_hdr frame {:?}", _rpc_udp_tc_hdr); + SCLogDebug!("rpc_udp_tc_data frame {:?}", _rpc_udp_tc_data); + } + } + + fn add_rpc_tcp_tc_pdu(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) -> Option<Frame> { + let rpc_tcp_tc_pdu = Frame::new(flow, stream_slice, input, rpc_tcp_len, NFSFrameType::RPCPdu as u8); + SCLogDebug!("rpc_tcp_pdu tc frame {:?}", rpc_tcp_tc_pdu); + rpc_tcp_tc_pdu + } + + fn add_rpc_tcp_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], rpc_tcp_len: i64) { + if rpc_tcp_len > 12 { + let _rpc_tcp_tc_hdr = Frame::new(flow, stream_slice, input, 12, NFSFrameType::RPCHdr as u8); + let _rpc_tcp_tc_data = Frame::new(flow, stream_slice, &input[12..], rpc_tcp_len - 12, NFSFrameType::RPCData as u8); + SCLogDebug!("rpc_tcp_tc_hdr frame {:?}", _rpc_tcp_tc_hdr); + SCLogDebug!("rpc_tcp_tc_data frame {:?}", _rpc_tcp_tc_data); + } + } + + fn add_nfs_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs_len: i64) { + if nfs_len > 0 { + let _nfs_tc_pdu = Frame::new(flow, stream_slice, input, nfs_len, NFSFrameType::NFSPdu as u8); + SCLogDebug!("nfs_tc_pdu frame {:?}", _nfs_tc_pdu); + let _nfs_res_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFSStatus as u8); + SCLogDebug!("nfs_tc_status frame {:?}", _nfs_res_status); + } + } + + fn add_nfs4_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nfs4_len: i64) { + if nfs4_len > 0 { + let _nfs4_tc_pdu = Frame::new(flow, stream_slice, input, nfs4_len, NFSFrameType::NFS4Pdu as u8); + SCLogDebug!("nfs4_tc_pdu frame {:?}", _nfs4_tc_pdu); + let _nfs4_tc_status = Frame::new(flow, stream_slice, input, 4, NFSFrameType::NFS4Status as u8); + SCLogDebug!("nfs4_tc_status frame {:?}", _nfs4_tc_status); + } + if nfs4_len > 8 { + let _nfs4_tc_hdr = Frame::new(flow, stream_slice, input, 8, NFSFrameType::NFS4Hdr as u8); + SCLogDebug!("nfs4_tc_hdr frame {:?}", _nfs4_tc_hdr); + let _nfs4_tc_ops = Frame::new(flow, stream_slice, &input[8..], nfs4_len - 8, NFSFrameType::NFS4Ops as u8); + SCLogDebug!("nfs4_tc_ops frame {:?}", _nfs4_tc_ops); + } + } + + fn post_gap_housekeeping_for_files(&mut self) + { + let mut post_gap_txs = false; + for tx in &mut self.transactions { + if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { + if f.post_gap_ts > 0 { + if self.ts > f.post_gap_ts { + tx.request_done = true; + tx.response_done = true; + filetracker_trunc(&mut f.file_tracker); + } else { + post_gap_txs = true; + } + } + } + } + self.check_post_gap_file_txs = post_gap_txs; + } + + /* after a gap we will consider all transactions complete for our + * direction. File transfer transactions are an exception. Those + * can handle gaps. For the file transactions we set the current + * (flow) time and prune them in 60 seconds if no update for them + * was received. */ + fn post_gap_housekeeping(&mut self, dir: Direction) + { + if self.ts_ssn_gap && dir == Direction::ToServer { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id); + tx.request_done = true; + } + } + } else if self.tc_ssn_gap && dir == Direction::ToClient { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(NFSTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id); + tx.request_done = true; + tx.response_done = true; + } + } + } + } + + pub fn process_request_record_lookup(&mut self, r: &RpcPacket, xidmap: &mut NFSRequestXidMap) { + match parse_nfs3_request_lookup(r.prog_data) { + Ok((_, lookup)) => { + SCLogDebug!("LOOKUP {:?}", lookup); + xidmap.file_name = lookup.name_vec; + }, + _ => { + self.set_event(NFSEvent::MalformedData); + }, + }; + } + + pub fn xidmap_handle2name(&mut self, xidmap: &mut NFSRequestXidMap) { + match self.namemap.get(&xidmap.file_handle) { + Some(n) => { + SCLogDebug!("xidmap_handle2name: name {:?}", n); + xidmap.file_name = n.to_vec(); + }, + _ => { + SCLogDebug!("xidmap_handle2name: object {:?} not found", + xidmap.file_handle); + }, + } + } + + /// complete request record + fn process_request_record(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcPacket) { + SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}", + r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); + + match r.progver { + 4 => { + self.add_nfs4_ts_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_request_record_v4(r) + }, + 3 => { + self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_request_record_v3(r) + }, + 2 => { + self.add_nfs_ts_frame(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_request_record_v2(r) + }, + _ => { }, + } + } + + pub fn new_file_tx(&mut self, file_handle: &[u8], file_name: &[u8], direction: Direction) + -> &mut NFSTransaction + { + let mut tx = self.new_tx(); + tx.file_name = file_name.to_vec(); + tx.file_handle = file_handle.to_vec(); + tx.is_file_tx = true; + + tx.type_data = Some(NFSTransactionTypeData::FILE(NFSTransactionFile::new())); + if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data { + d.direction = direction; + d.file_tracker.tx_id = tx.id - 1; + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + tx.tx_data.init_files_opened(); + tx.tx_data.file_tx = if direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; // TODO direction to flag func? + SCLogDebug!("new_file_tx: TX FILE created: ID {} NAME {}", + tx.id, String::from_utf8_lossy(file_name)); + self.transactions.push(tx); + let tx_ref = self.transactions.last_mut(); + return tx_ref.unwrap(); + } + + pub fn get_file_tx_by_handle(&mut self, file_handle: &[u8], direction: Direction) + -> Option<&mut NFSTransaction> + { + let fh = file_handle.to_vec(); + for tx in &mut self.transactions { + if let Some(NFSTransactionTypeData::FILE(ref mut d)) = tx.type_data { + if tx.is_file_tx && !tx.is_file_closed && + direction == d.direction && + tx.file_handle == fh + { + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + SCLogDebug!("Found NFS file TX with ID {} XID {:04X}", tx.id, tx.xid); + return Some(tx); + } + } + } + SCLogDebug!("Failed to find NFS TX with handle {:?}", file_handle); + return None; + } + + pub fn process_write_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { + let mut fill_bytes = 0; + let pad = w.count % 4; + if pad != 0 { + fill_bytes = 4 - pad; + } + + // linux defines a max of 1mb. Allow several multiples. + if w.count == 0 || w.count > 16777216 { + return 0; + } + + // for now assume that stable FILE_SYNC flags means a single chunk + let is_last = w.stable == 2; + let file_handle = w.handle.value.to_vec(); + let file_name = if let Some(name) = self.namemap.get(w.handle.value) { + SCLogDebug!("WRITE name {:?}", name); + name.to_vec() + } else { + SCLogDebug!("WRITE object {:?} not found", w.handle.value); + Vec::new() + }; + + let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) { + Some(tx) => { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, w.file_data, w.offset, + w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); + tdf.chunk_count += 1; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.response_done = true; + tx.is_file_closed = true; + } + true + } else { + false + } + }, + None => { false }, + }; + if !found { + let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, w.file_data, w.offset, + w.file_len, fill_bytes as u8, is_last, &r.hdr.xid); + tx.procedure = NFSPROC3_WRITE; + tx.xid = r.hdr.xid; + tx.is_first = true; + tx.nfs_version = r.progver as u16; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.request_done = true; + tx.is_file_closed = true; + } + } + } + if !self.is_udp { + self.ts_chunk_xid = r.hdr.xid; + debug_validate_bug_on!(w.file_data.len() as u32 > w.count); + self.ts_chunk_left = w.count - w.file_data.len() as u32; + self.ts_chunk_fh = file_handle; + SCLogDebug!("REQUEST chunk_xid {:04X} chunk_left {}", self.ts_chunk_xid, self.ts_chunk_left); + } + 0 + } + + fn process_partial_write_request_record<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs3RequestWrite<'b>) -> u32 { + SCLogDebug!("REQUEST {} procedure {} blob size {}", r.hdr.xid, r.procedure, r.prog_data.len()); + + let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); + xidmap.file_handle = w.handle.value.to_vec(); + self.requestmap.insert(r.hdr.xid, xidmap); + + return self.process_write_record(r, w); + } + + fn process_reply_record(&mut self, flow: *const Flow, stream_slice: &StreamSlice, r: &RpcReplyPacket) -> u32 { + let mut xidmap; + match self.requestmap.remove(&r.hdr.xid) { + Some(p) => { xidmap = p; }, + _ => { + SCLogDebug!("REPLY: xid {:04X} NOT FOUND. GAPS? TS:{} TC:{}", + r.hdr.xid, self.ts_ssn_gap, self.tc_ssn_gap); + + // TODO we might be able to try to infer from the size + data + // that this is a READ reply and pass the data to the file API anyway? + return 0; + }, + } + SCLogDebug!("process_reply_record: removed xid {:04X} from requestmap", + r.hdr.xid); + + if self.nfs_version == 0 { + self.nfs_version = xidmap.progver as u16; + } + + match xidmap.progver { + 2 => { + SCLogDebug!("NFSv2 reply record"); + self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_reply_record_v2(r, &xidmap); + return 0; + }, + 3 => { + SCLogDebug!("NFSv3 reply record"); + self.add_nfs_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_reply_record_v3(r, &mut xidmap); + return 0; + }, + 4 => { + SCLogDebug!("NFSv4 reply record"); + self.add_nfs4_tc_frames(flow, stream_slice, r.prog_data, r.prog_data_size as i64); + self.process_reply_record_v4(r, &mut xidmap); + return 0; + }, + _ => { + SCLogDebug!("Invalid NFS version"); + self.set_event(NFSEvent::NonExistingVersion); + return 0; + }, + } + } + + // update in progress chunks for file transfers + // return how much data we consumed + fn filetracker_update(&mut self, direction: Direction, data: &[u8], gap_size: u32) -> u32 { + let mut chunk_left = if direction == Direction::ToServer { + self.ts_chunk_left + } else { + self.tc_chunk_left + }; + if chunk_left == 0 { + return 0 + } + let xid = if direction == Direction::ToServer { + self.ts_chunk_xid + } else { + self.tc_chunk_xid + }; + SCLogDebug!("filetracker_update: chunk left {}, input {} chunk_xid {:04X}", chunk_left, data.len(), xid); + + let file_handle; + // we have the data that we expect + if chunk_left <= data.len() as u32 { + chunk_left = 0; + + if direction == Direction::ToServer { + self.ts_chunk_xid = 0; + file_handle = self.ts_chunk_fh.to_vec(); + self.ts_chunk_fh.clear(); + } else { + self.tc_chunk_xid = 0; + + // chunk done, remove requestmap entry + match self.requestmap.remove(&xid) { + None => { + SCLogDebug!("no file handle found for XID {:04X}", xid); + return 0 + }, + Some(xidmap) => { + file_handle = xidmap.file_handle.to_vec(); + }, + } + } + } else { + chunk_left -= data.len() as u32; + + if direction == Direction::ToServer { + file_handle = self.ts_chunk_fh.to_vec(); + } else { + // see if we have a file handle to work on + match self.requestmap.get(&xid) { + None => { + SCLogDebug!("no file handle found for XID {:04X}", xid); + return 0 + }, + Some(xidmap) => { + file_handle = xidmap.file_handle.to_vec(); + }, + } + } + } + + if direction == Direction::ToServer { + self.ts_chunk_left = chunk_left; + } else { + self.tc_chunk_left = chunk_left; + } + + let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap; + // get the tx and update it + let consumed = match self.get_file_tx_by_handle(&file_handle, direction) { + Some(tx) => { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if ssn_gap { + let queued_data = tdf.file_tracker.get_queued_size(); + if queued_data > 2000000 { // TODO should probably be configurable + SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data); + filetracker_trunc(&mut tdf.file_tracker); + } + } + + // reset timestamp if we get called after a gap + if tdf.post_gap_ts > 0 { + tdf.post_gap_ts = 0; + } + + tdf.chunk_count += 1; + let cs = filetracker_update(&mut tdf.file_tracker, data, gap_size); + /* see if we need to close the tx */ + if tdf.file_tracker.is_done() { + if direction == Direction::ToClient { + tx.response_done = true; + tx.is_file_closed = true; + SCLogDebug!("TX {} response is done now that the file track is ready", tx.id); + } else { + tx.request_done = true; + tx.is_file_closed = true; + SCLogDebug!("TX {} request is done now that the file track is ready", tx.id); + } + } + cs + } else { + 0 + } + }, + None => { 0 }, + }; + return consumed; + } + + /// xidmapr is an Option as it's already removed from the map if we + /// have a complete record. Otherwise we do a lookup ourselves. + pub fn process_read_record<'b>(&mut self, r: &RpcReplyPacket<'b>, + reply: &NfsReplyRead<'b>, xidmapr: Option<&NFSRequestXidMap>) -> u32 + { + let file_name; + let file_handle; + let chunk_offset; + let nfs_version; + + let mut fill_bytes = 0; + let pad = reply.count % 4; + if pad != 0 { + fill_bytes = 4 - pad; + } + + // linux defines a max of 1mb. Allow several multiples. + if reply.count == 0 || reply.count > 16777216 { + return 0; + } + + match xidmapr { + Some(xidmap) => { + file_name = xidmap.file_name.to_vec(); + file_handle = xidmap.file_handle.to_vec(); + chunk_offset = xidmap.chunk_offset; + nfs_version = xidmap.progver; + }, + None => { + if let Some(xidmap) = self.requestmap.get(&r.hdr.xid) { + file_name = xidmap.file_name.to_vec(); + file_handle = xidmap.file_handle.to_vec(); + chunk_offset = xidmap.chunk_offset; + nfs_version = xidmap.progver; + } else { + return 0; + } + }, + } + SCLogDebug!("chunk_offset {}", chunk_offset); + + let mut is_last = reply.eof; + SCLogDebug!("XID {} is_last {} fill_bytes {} reply.count {} reply.data_len {} reply.data.len() {}", + r.hdr.xid, is_last, fill_bytes, reply.count, reply.data_len, reply.data.len()); + + if nfs_version == 2 { + let size = match parse_nfs2_attribs(reply.attr_blob) { + Ok((_, ref attr)) => attr.asize, + _ => 0, + }; + SCLogDebug!("NFSv2 READ reply record: File size {}. Offset {} data len {}: total {}", + size, chunk_offset, reply.data_len, chunk_offset + reply.data_len as u64); + + if size as u64 == chunk_offset + reply.data_len as u64 { + is_last = true; + } + + } + + let is_partial = reply.data.len() < reply.count as usize; + SCLogDebug!("partial data? {}", is_partial); + + let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToClient) { + Some(tx) => { + SCLogDebug!("updated TX {:?}", tx); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, reply.data, chunk_offset, + reply.count, fill_bytes as u8, is_last, &r.hdr.xid); + tdf.chunk_count += 1; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.rpc_response_status = r.reply_state; + tx.nfs_response_status = reply.status; + tx.is_last = true; + tx.request_done = true; + + /* if this is a partial record we will close the tx + * when we've received the final data */ + if !is_partial { + tx.response_done = true; + SCLogDebug!("TX {} is DONE", tx.id); + } + } + true + } else { + false + } + }, + None => { false }, + }; + if !found { + let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToClient); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, reply.data, chunk_offset, + reply.count, fill_bytes as u8, is_last, &r.hdr.xid); + tx.procedure = if nfs_version < 4 { NFSPROC3_READ } else { NFSPROC4_READ }; + tx.xid = r.hdr.xid; + tx.is_first = true; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.rpc_response_status = r.reply_state; + tx.nfs_response_status = reply.status; + tx.is_last = true; + tx.request_done = true; + + /* if this is a partial record we will close the tx + * when we've received the final data */ + if !is_partial { + tx.response_done = true; + SCLogDebug!("TX {} is DONE", tx.id); + } + } + } + } + + if !self.is_udp { + self.tc_chunk_xid = r.hdr.xid; + debug_validate_bug_on!(reply.data.len() as u32 > reply.count); + self.tc_chunk_left = reply.count - reply.data.len() as u32; + } + + SCLogDebug!("REPLY {} to procedure {} blob size {} / {}: chunk_left {} chunk_xid {:04X}", + r.hdr.xid, NFSPROC3_READ, r.prog_data.len(), reply.count, self.tc_chunk_left, + self.tc_chunk_xid); + 0 + } + + fn process_partial_read_reply_record<'b>(&mut self, r: &RpcReplyPacket<'b>, reply: &NfsReplyRead<'b>) -> u32 { + SCLogDebug!("REPLY {} to procedure READ blob size {} / {}", + r.hdr.xid, r.prog_data.len(), reply.count); + + return self.process_read_record(r, reply, None); + } + + fn peek_reply_record(&mut self, r: &RpcPacketHeader) -> u32 { + if let Some(xidmap) = self.requestmap.get(&r.xid) { + return xidmap.procedure; + } else { + SCLogDebug!("REPLY: xid {} NOT FOUND", r.xid); + return 0; + } + } + + pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> AppLayerResult { + SCLogDebug!("parse_tcp_data_ts_gap ({})", gap_size); + let gap = vec![0; gap_size as usize]; + let consumed = self.filetracker_update(Direction::ToServer, &gap, gap_size); + if consumed > gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); + return AppLayerResult::ok(); + } + self.ts_ssn_gap = true; + self.ts_gap = true; + SCLogDebug!("parse_tcp_data_ts_gap ({}) done", gap_size); + return AppLayerResult::ok(); + } + + pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> AppLayerResult { + SCLogDebug!("parse_tcp_data_tc_gap ({})", gap_size); + let gap = vec![0; gap_size as usize]; + let consumed = self.filetracker_update(Direction::ToClient, &gap, gap_size); + if consumed > gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed, gap_size); + return AppLayerResult::ok(); + } + self.tc_ssn_gap = true; + self.tc_gap = true; + SCLogDebug!("parse_tcp_data_tc_gap ({}) done", gap_size); + return AppLayerResult::ok(); + } + + /// Handle partial records + fn parse_tcp_partial_data_ts<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8], + phdr: &RpcRequestPacketPartial, rec_size: usize) -> AppLayerResult { + // special case: avoid buffering file write blobs + // as these can be large. + if rec_size >= 512 && cur_i.len() >= 44 { + // large record, likely file xfer + SCLogDebug!("large record {}, likely file xfer", rec_size); + + // quick peek, are we in WRITE mode? + if phdr.procedure == NFSPROC3_WRITE { + SCLogDebug!("CONFIRMED WRITE: large record {}, file chunk xfer", rec_size); + + // lets try to parse the RPC record. Might fail with Incomplete. + match parse_rpc(cur_i, false) { + Ok((_rem, ref hdr)) => { + // we got here because rec_size > input, so we should never have + // remaining data + debug_validate_bug_on!(!_rem.is_empty()); + + match parse_nfs3_request_write(hdr.prog_data, false) { + Ok((_, ref w)) => { + // deal with the partial nfs write data + self.process_partial_write_request_record(hdr, w); + return AppLayerResult::ok(); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + Err(Err::Incomplete(_)) => { + // this is normal, fall through to incomplete handling + } + } + } + Err(Err::Incomplete(_)) => { + // size check was done for a minimal RPC record size, + // so Incomplete is normal. + SCLogDebug!("TS data incomplete"); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + } + } + } + // make sure we pass a value higher than current input + // but lower than the record size + let n1 = cmp::max(cur_i.len(), 1024); + let n2 = cmp::min(n1, rec_size); + return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32); + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { + let mut cur_i = stream_slice.as_slice(); + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToServer, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + if self.ts_gap { + SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len()); + + let mut _cnt = 0; + while !cur_i.is_empty() { + _cnt += 1; + match nfs_probe(cur_i, Direction::ToServer) { + 1 => { + SCLogDebug!("expected data found"); + self.ts_gap = false; + break; + }, + 0 => { + SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", + cur_i.len(), _cnt); + return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32); + }, + -1 => { + cur_i = &cur_i[1..]; + if cur_i.is_empty() { + SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt); + } + }, + _ => { + return AppLayerResult::err(); + }, + } + } + SCLogDebug!("TS GAP handling done (input {})", cur_i.len()); + } + + while !cur_i.is_empty() { // min record size + self.add_rpc_tcp_ts_pdu(flow, stream_slice, cur_i, cur_i.len() as i64); + match parse_rpc_request_partial(cur_i) { + Ok((_, ref rpc_phdr)) => { + let rec_size = (rpc_phdr.hdr.frag_len + 4) as usize; + + // Handle partial records + if rec_size > cur_i.len() { + return self.parse_tcp_partial_data_ts(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size); + } + + // we have the full records size worth of data, + // let's parse it. Errors lead to event, but are + // not fatal as we already have enough info to + // go to the next record. + match parse_rpc(cur_i, true) { + Ok((_, ref rpc_record)) => { + self.add_rpc_tcp_ts_creds(flow, stream_slice, &cur_i[RPC_TCP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64); + self.process_request_record(flow, stream_slice, rpc_record); + } + Err(Err::Incomplete(_)) => { + self.set_event(NFSEvent::MalformedData); + } + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + } + } + cur_i = &cur_i[rec_size..]; + } + Err(Err::Incomplete(needed)) => { + if let Needed::Size(n) = needed { + SCLogDebug!("Not enough data for partial RPC header {:?}", needed); + // 28 is the partial RPC header size parse_rpc_request_partial + // looks for. + let n = usize::from(n); + let need = if n > 28 { n } else { 28 }; + return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32); + } + return AppLayerResult::err(); + } + /* This error is fatal. If we failed to parse the RPC hdr we don't + * have a length and we don't know where the next record starts. */ + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + } + }; + + self.post_gap_housekeeping(Direction::ToServer); + if self.check_post_gap_file_txs && !self.post_gap_files_checked { + self.post_gap_housekeeping_for_files(); + self.post_gap_files_checked = true; + } + + AppLayerResult::ok() + } + + /// Handle partial records + fn parse_tcp_partial_data_tc<'b>(&mut self, base_input: &'b[u8], cur_i: &'b[u8], + phdr: &RpcPacketHeader, rec_size: usize) -> AppLayerResult { + // special case: avoid buffering file read blobs + // as these can be large. + if rec_size >= 512 && cur_i.len() >= 128 {//36 { + // large record, likely file xfer + SCLogDebug!("large record {}, likely file xfer", rec_size); + + // quick peek, are in READ mode? + if self.peek_reply_record(phdr) == NFSPROC3_READ { + SCLogDebug!("CONFIRMED large READ record {}, likely file chunk xfer", rec_size); + + // we should have enough data to parse the RPC record + match parse_rpc_reply(cur_i, false) { + Ok((_rem, ref hdr)) => { + // we got here because rec_size > input, so we should never have + // remaining data + debug_validate_bug_on!(!_rem.is_empty()); + + match parse_nfs3_reply_read(hdr.prog_data, false) { + Ok((_, ref r)) => { + // deal with the partial nfs read data + self.process_partial_read_reply_record(hdr, r); + return AppLayerResult::ok(); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + Err(Err::Incomplete(_)) => { + // this is normal, fall through to incomplete handling + } + } + } + Err(Err::Incomplete(_)) => { + // size check was done for a minimal RPC record size, + // so Incomplete is normal. + SCLogDebug!("TC data incomplete"); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + } + } + } + // make sure we pass a value higher than current input + // but lower than the record size + let n1 = cmp::max(cur_i.len(), 1024); + let n2 = cmp::min(n1, rec_size); + return AppLayerResult::incomplete((base_input.len() - cur_i.len()) as u32, n2 as u32); + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { + let mut cur_i = stream_slice.as_slice(); + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToClient, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + if self.tc_gap { + SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len()); + + let mut _cnt = 0; + while !cur_i.is_empty() { + _cnt += 1; + match nfs_probe(cur_i, Direction::ToClient) { + 1 => { + SCLogDebug!("expected data found"); + self.tc_gap = false; + break; + }, + 0 => { + SCLogDebug!("incomplete, queue and retry with the next block (input {}). Looped {} times.", + cur_i.len(), _cnt); + return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, (cur_i.len() + 1) as u32); + }, + -1 => { + cur_i = &cur_i[1..]; + if cur_i.is_empty() { + SCLogDebug!("all post-GAP data in this chunk was bad. Looped {} times.", _cnt); + } + }, + _ => { + return AppLayerResult::err(); + } + } + } + SCLogDebug!("TC GAP handling done (input {})", cur_i.len()); + } + + while !cur_i.is_empty() { + self.add_rpc_tcp_tc_pdu(flow, stream_slice, cur_i, cur_i.len() as i64); + match parse_rpc_packet_header(cur_i) { + Ok((_, ref rpc_phdr)) => { + let rec_size = (rpc_phdr.frag_len + 4) as usize; + // see if we have all data available + if rec_size > cur_i.len() { + return self.parse_tcp_partial_data_tc(stream_slice.as_slice(), cur_i, rpc_phdr, rec_size); + } + + // we have the full data of the record, lets parse + match parse_rpc_reply(cur_i, true) { + Ok((_, ref rpc_record)) => { + self.add_rpc_tcp_tc_frames(flow, stream_slice, cur_i, cur_i.len() as i64); + self.process_reply_record(flow, stream_slice, rpc_record); + } + Err(Err::Incomplete(_)) => { + // we shouldn't get incomplete as we have the full data + // so if we got incomplete anyway it's the data that is + // bad. + self.set_event(NFSEvent::MalformedData); + } + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + } + } + cur_i = &cur_i[rec_size..]; // progress input past parsed record + } + Err(Err::Incomplete(needed)) => { + if let Needed::Size(n) = needed { + SCLogDebug!("Not enough data for partial RPC header {:?}", needed); + // 12 is the partial RPC header size parse_rpc_packet_header + // looks for. + let n = usize::from(n); + let need = if n > 12 { n } else { 12 }; + return AppLayerResult::incomplete(stream_slice.len() - cur_i.len() as u32, need as u32); + } + return AppLayerResult::err(); + } + /* This error is fatal. If we failed to parse the RPC hdr we don't + * have a length and we don't know where the next record starts. */ + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + self.set_event(NFSEvent::MalformedData); + SCLogDebug!("Parsing failed: {:?}", _e); + return AppLayerResult::err(); + } + } + }; + self.post_gap_housekeeping(Direction::ToClient); + if self.check_post_gap_file_txs && !self.post_gap_files_checked { + self.post_gap_housekeeping_for_files(); + self.post_gap_files_checked = true; + } + AppLayerResult::ok() + } + /// Parsing function + pub fn parse_udp_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + SCLogDebug!("parse_udp_ts ({})", input.len()); + self.add_rpc_udp_ts_pdu(flow, stream_slice, input, input.len() as i64); + if !input.is_empty() { + match parse_rpc_udp_request(input) { + Ok((_, ref rpc_record)) => { + self.is_udp = true; + self.add_rpc_udp_ts_creds(flow, stream_slice, &input[RPC_UDP_PRE_CREDS..], (rpc_record.creds_len + 8) as i64); + match rpc_record.progver { + 3 => { + self.process_request_record(flow, stream_slice, rpc_record); + }, + 2 => { + self.add_nfs_ts_frame(flow, stream_slice, rpc_record.prog_data, rpc_record.prog_data_size as i64); + self.process_request_record_v2(rpc_record); + }, + _ => { }, + } + }, + Err(Err::Incomplete(_)) => { + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("Parsing failed: {:?}", _e); + } + } + } + AppLayerResult::ok() + } + + /// Parsing function + pub fn parse_udp_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + SCLogDebug!("parse_udp_tc ({})", input.len()); + self.add_rpc_udp_tc_pdu(flow, stream_slice, input, input.len() as i64); + if !input.is_empty() { + match parse_rpc_udp_reply(input) { + Ok((_, ref rpc_record)) => { + self.is_udp = true; + self.add_rpc_udp_tc_frames(flow, stream_slice, input, input.len() as i64); + self.process_reply_record(flow, stream_slice, rpc_record); + }, + Err(Err::Incomplete(_)) => { + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("Parsing failed: {:?}", _e); + } + } + } + AppLayerResult::ok() + } +} + +/// Returns *mut NFSState +#[no_mangle] +pub extern "C" fn rs_nfs_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = NFSState::new(); + let boxed = Box::new(state); + SCLogDebug!("allocating state"); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut NFSState as void pointer +#[no_mangle] +pub extern "C" fn rs_nfs_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + SCLogDebug!("freeing state"); + std::mem::drop(unsafe { Box::from_raw(state as *mut NFSState) }); +} + +/// C binding parse a NFS TCP request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_parse_request(flow: *const Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult +{ + let state = cast_pointer!(state, NFSState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_nfs_parse_request_tcp_gap(state, stream_slice.gap_size()); + } + SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_ts(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_nfs_parse_request_tcp_gap( + state: &mut NFSState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_ts_gap(input_len) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_parse_response(flow: *const Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult +{ + let state = cast_pointer!(state, NFSState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_nfs_parse_response_tcp_gap(state, stream_slice.gap_size()); + } + SCLogDebug!("parsing {} bytes of response data", stream_slice.len()); + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_tc(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_nfs_parse_response_tcp_gap( + state: &mut NFSState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_tc_gap(input_len) +} + +/// C binding to parse an NFS/UDP request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_parse_request_udp(f: *const Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult +{ + let state = cast_pointer!(state, NFSState); + + SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); + state.parse_udp_ts(f, &stream_slice) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_parse_response_udp(f: *const Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult +{ + let state = cast_pointer!(state, NFSState); + SCLogDebug!("parsing {} bytes of response data", stream_slice.len()); + state.parse_udp_tc(f, &stream_slice) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_state_get_tx_count(state: *mut std::os::raw::c_void) + -> u64 +{ + let state = cast_pointer!(state, NFSState); + SCLogDebug!("rs_nfs_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_state_get_tx(state: *mut std::os::raw::c_void, + tx_id: u64) + -> *mut std::os::raw::c_void +{ + let state = cast_pointer!(state, NFSState); + match state.get_tx_by_id(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_state_tx_free(state: *mut std::os::raw::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state, NFSState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_tx_get_alstate_progress(tx: *mut std::os::raw::c_void, + direction: u8) + -> std::os::raw::c_int +{ + let tx = cast_pointer!(tx, NFSTransaction); + if direction == Direction::ToServer.into() && tx.request_done { + SCLogDebug!("TOSERVER progress 1"); + return 1; + } else if direction == Direction::ToClient.into() && tx.response_done { + SCLogDebug!("TOCLIENT progress 1"); + return 1; + } else { + SCLogDebug!("{} progress 0", direction); + return 0; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, NFSTransaction); + return &mut tx.tx_data; +} + +export_state_data_get!(rs_nfs_get_state_data, NFSState); + +/// return procedure(s) in the tx. At 0 return the main proc, +/// otherwise get procs from the 'file_additional_procs'. +/// Keep calling until 0 is returned. +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_tx_get_procedures(tx: &mut NFSTransaction, + i: u16, + procedure: *mut u32) + -> u8 +{ + if i == 0 { + *procedure = tx.procedure; + return 1; + } + + if !tx.is_file_tx { + return 0; + } + + /* file tx handling follows */ + + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let idx = i as usize - 1; + if idx < tdf.file_additional_procs.len() { + let p = tdf.file_additional_procs[idx]; + *procedure = p; + return 1; + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_tx_get_version(tx: &mut NFSTransaction, + version: *mut u32) +{ + *version = tx.nfs_version as u32; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_init(context: &'static mut SuricataFileContext) +{ + SURICATA_NFS_FILE_CONFIG = Some(context); +} + +fn nfs_probe_dir(i: &[u8], rdir: *mut u8) -> i8 { + match parse_rpc_packet_header(i) { + Ok((_, ref hdr)) => { + let dir = if hdr.msgtype == 0 { + Direction::ToServer + } else { + Direction::ToClient + }; + unsafe { *rdir = dir as u8 }; + return 1; + }, + Err(Err::Incomplete(_)) => { + return 0; + }, + Err(_) => { + return -1; + }, + } +} + +pub fn nfs_probe(i: &[u8], direction: Direction) -> i32 { + if direction == Direction::ToClient { + match parse_rpc_reply(i, false) { + Ok((_, ref rpc)) => { + if rpc.hdr.frag_len >= 24 && rpc.hdr.frag_len <= 35000 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { + SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); + return 1; + } else { + return -1; + } + }, + Err(Err::Incomplete(_)) => { + match parse_rpc_packet_header (i) { + Ok((_, ref rpc_hdr)) => { + if rpc_hdr.frag_len >= 24 && rpc_hdr.frag_len <= 35000 && rpc_hdr.xid != 0 && rpc_hdr.msgtype == 1 { + SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc_hdr.frag_len, rpc_hdr.xid, rpc_hdr.msgtype); + return 1; + } else { + return -1; + } + }, + Err(Err::Incomplete(_)) => { + return 0; + }, + Err(_) => { + return -1; + }, + } + }, + Err(_) => { + return -1; + }, + } + } else { + match parse_rpc(i, false) { + Ok((_, ref rpc)) => { + if rpc.hdr.frag_len >= 40 && rpc.hdr.msgtype == 0 && + rpc.rpcver == 2 && (rpc.progver == 3 || rpc.progver == 4) && + rpc.program == 100003 && + rpc.procedure <= NFSPROC3_COMMIT + { + return rpc_auth_type_known(rpc.creds_flavor) as i32; + } else { + return -1; + } + }, + Err(Err::Incomplete(_)) => { + return 0; + }, + Err(_) => { + return -1; + }, + } + } +} + +pub fn nfs_probe_udp(i: &[u8], direction: Direction) -> i32 { + if direction == Direction::ToClient { + match parse_rpc_udp_reply(i) { + Ok((_, ref rpc)) => { + if i.len() >= 32 && rpc.hdr.msgtype == 1 && rpc.reply_state == 0 && rpc.accept_state == 0 { + SCLogDebug!("TC PROBE LEN {} XID {} TYPE {}", rpc.hdr.frag_len, rpc.hdr.xid, rpc.hdr.msgtype); + return 1; + } else { + return -1; + } + }, + Err(_) => { + return -1; + }, + } + } else { + match parse_rpc_udp_request(i) { + Ok((_, ref rpc)) => { + if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 3 && rpc.program == 100003 { + return 1; + } else if i.len() >= 48 && rpc.hdr.msgtype == 0 && rpc.progver == 2 && rpc.program == 100003 { + SCLogDebug!("NFSv2!"); + return 1; + } else { + return -1; + } + }, + Err(_) => { + return -1; + }, + } + } +} + +/// MIDSTREAM +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_probe_ms( + _flow: *const Flow, + direction: u8, input: *const u8, + len: u32, rdir: *mut u8) -> AppProto +{ + let slice: &[u8] = build_slice!(input, len as usize); + SCLogDebug!("rs_nfs_probe_ms: probing direction {:02x}", direction); + let mut adirection : u8 = 0; + match nfs_probe_dir(slice, &mut adirection) { + 1 => { + if adirection == Direction::ToServer.into() { + SCLogDebug!("nfs_probe_dir said Direction::ToServer"); + } else { + SCLogDebug!("nfs_probe_dir said Direction::ToClient"); + } + match nfs_probe(slice, adirection.into()) { + 1 => { + SCLogDebug!("nfs_probe success: dir {:02x} adir {:02x}", direction, adirection); + if (direction & DIR_BOTH) != adirection { + *rdir = adirection; + } + ALPROTO_NFS + }, + 0 => { ALPROTO_UNKNOWN }, + _ => { ALPROTO_FAILED }, + } + }, + 0 => { + ALPROTO_UNKNOWN + }, + _ => { + ALPROTO_FAILED + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_probe(_f: *const Flow, + direction: u8, + input: *const u8, + len: u32, + _rdir: *mut u8) + -> AppProto +{ + let slice: &[u8] = build_slice!(input, len as usize); + SCLogDebug!("rs_nfs_probe: running probe"); + match nfs_probe(slice, direction.into()) { + 1 => { ALPROTO_NFS }, + -1 => { ALPROTO_FAILED }, + _ => { ALPROTO_UNKNOWN }, + } +} + +/// TOSERVER probe function +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_probe_udp_ts(_f: *const Flow, + _direction: u8, + input: *const u8, + len: u32, + _rdir: *mut u8) + -> AppProto +{ + let slice: &[u8] = build_slice!(input, len as usize); + match nfs_probe_udp(slice, Direction::ToServer) { + 1 => { ALPROTO_NFS }, + -1 => { ALPROTO_FAILED }, + _ => { ALPROTO_UNKNOWN }, + } +} + +/// TOCLIENT probe function +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_probe_udp_tc(_f: *const Flow, + _direction: u8, + input: *const u8, + len: u32, + _rdir: *mut u8) + -> AppProto +{ + let slice: &[u8] = build_slice!(input, len as usize); + match nfs_probe_udp(slice, Direction::ToClient) { + 1 => { ALPROTO_NFS }, + -1 => { ALPROTO_FAILED }, + _ => { ALPROTO_UNKNOWN }, + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"nfs\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_register_parser() { + let default_port = CString::new("[2049]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_nfs_state_new, + state_free: rs_nfs_state_free, + tx_free: rs_nfs_state_tx_free, + parse_ts: rs_nfs_parse_request, + parse_tc: rs_nfs_parse_response, + get_tx_count: rs_nfs_state_get_tx_count, + get_tx: rs_nfs_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_nfs_tx_get_alstate_progress, + get_eventinfo: Some(NFSEvent::get_event_info), + get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: Some(rs_nfs_gettxfiles), + get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>), + get_tx_data: rs_nfs_get_tx_data, + get_state_data: rs_nfs_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_NFS = alproto; + + let midstream = conf_get_bool("stream.midstream"); + if midstream { + if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_TCP, + parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN, + rs_nfs_probe_ms, rs_nfs_probe_ms) == 0 { + SCLogDebug!("No NFSTCP app-layer configuration, enabling NFSTCP detection TCP detection on port {:?}.", + default_port); + /* register 'midstream' probing parsers if midstream is enabled. */ + AppLayerProtoDetectPPRegister(IPPROTO_TCP, + default_port.as_ptr(), ALPROTO_NFS, 0, + NFS_MIN_FRAME_LEN, Direction::ToServer.into(), + rs_nfs_probe_ms, rs_nfs_probe_ms); + } + } else { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, + default_port.as_ptr(), ALPROTO_NFS, 0, + NFS_MIN_FRAME_LEN, Direction::ToServer.into(), + rs_nfs_probe, rs_nfs_probe); + } + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust nfs parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for nfs."); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_nfs_udp_register_parser() { + let default_port = CString::new("[2049]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_UDP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_nfs_state_new, + state_free: rs_nfs_state_free, + tx_free: rs_nfs_state_tx_free, + parse_ts: rs_nfs_parse_request_udp, + parse_tc: rs_nfs_parse_response_udp, + get_tx_count: rs_nfs_state_get_tx_count, + get_tx: rs_nfs_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_nfs_tx_get_alstate_progress, + get_eventinfo: Some(NFSEvent::get_event_info), + get_eventinfo_byid : Some(NFSEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: Some(rs_nfs_gettxfiles), + get_tx_iterator: Some(applayer::state_get_tx_iterator::<NFSState, NFSTransaction>), + get_tx_data: rs_nfs_get_tx_data, + get_state_data: rs_nfs_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: Some(NFSFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(NFSFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_NFS = alproto; + + if AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), IPPROTO_UDP, + parser.name, ALPROTO_NFS, 0, NFS_MIN_FRAME_LEN, + rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc) == 0 { + SCLogDebug!("No NFSUDP app-layer configuration, enabling NFSUDP detection UDP detection on port {:?}.", + default_port); + AppLayerProtoDetectPPRegister(IPPROTO_UDP, + default_port.as_ptr(), ALPROTO_NFS, 0, + NFS_MIN_FRAME_LEN, Direction::ToServer.into(), + rs_nfs_probe_udp_ts, rs_nfs_probe_udp_tc); + } + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + if let Some(val) = conf_get("app-layer.protocols.nfs.max-tx") { + if let Ok(v) = val.parse::<usize>() { + NFS_MAX_TX = v; + } else { + SCLogError!("Invalid value for nfs.max-tx"); + } + } + SCLogDebug!("Rust nfs parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for nfs."); + } +} diff --git a/rust/src/nfs/nfs2.rs b/rust/src/nfs/nfs2.rs new file mode 100644 index 0000000..f8000b4 --- /dev/null +++ b/rust/src/nfs/nfs2.rs @@ -0,0 +1,122 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Victor Julien + +use crate::nfs::nfs::*; +use crate::nfs::types::*; +use crate::nfs::rpc_records::*; +use crate::nfs::nfs2_records::*; + +use nom7::IResult; +use nom7::number::streaming::be_u32; + +impl NFSState { + /// complete request record + pub fn process_request_record_v2(&mut self, r: &RpcPacket) { + SCLogDebug!("NFSv2: REQUEST {} procedure {} ({}) blob size {}", + r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); + + let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); + let aux_file_name = Vec::new(); + + if r.procedure == NFSPROC3_LOOKUP { + match parse_nfs2_request_lookup(r.prog_data) { + Ok((_, ar)) => { + xidmap.file_handle = ar.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + }, + _ => { + self.set_event(NFSEvent::MalformedData); + }, + }; + } else if r.procedure == NFSPROC3_READ { + match parse_nfs2_request_read(r.prog_data) { + Ok((_, read_record)) => { + xidmap.chunk_offset = read_record.offset as u64; + xidmap.file_handle = read_record.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + }, + _ => { + self.set_event(NFSEvent::MalformedData); + }, + }; + } + + if !(r.procedure == NFSPROC3_COMMIT || // commit handled separately + r.procedure == NFSPROC3_WRITE || // write handled in file tx + r.procedure == NFSPROC3_READ) // read handled in file tx at reply + { + let mut tx = self.new_tx(); + tx.xid = r.hdr.xid; + tx.procedure = r.procedure; + tx.request_done = true; + tx.file_name = xidmap.file_name.to_vec(); + tx.file_handle = xidmap.file_handle.to_vec(); + tx.nfs_version = r.progver as u16; + + if r.procedure == NFSPROC3_RENAME { + tx.type_data = Some(NFSTransactionTypeData::RENAME(aux_file_name)); + } + + tx.auth_type = r.creds_flavor; + #[allow(clippy::single_match)] + match r.creds { + RpcRequestCreds::Unix(ref u) => { + tx.request_machine_name = u.machine_name_buf.to_vec(); + tx.request_uid = u.uid; + tx.request_gid = u.gid; + }, + _ => { }, + } + SCLogDebug!("NFSv2: TX created: ID {} XID {} PROCEDURE {}", + tx.id, tx.xid, tx.procedure); + self.transactions.push(tx); + } + + SCLogDebug!("NFSv2: TS creating xidmap {}", r.hdr.xid); + self.requestmap.insert(r.hdr.xid, xidmap); + } + + pub fn process_reply_record_v2(&mut self, r: &RpcReplyPacket, xidmap: &NFSRequestXidMap) { + let mut nfs_status = 0; + let resp_handle = Vec::new(); + + if xidmap.procedure == NFSPROC3_READ { + match parse_nfs2_reply_read(r.prog_data) { + Ok((_, ref reply)) => { + SCLogDebug!("NFSv2: READ reply record"); + self.process_read_record(r, reply, Some(xidmap)); + nfs_status = reply.status; + }, + _ => { + self.set_event(NFSEvent::MalformedData); + }, + } + } else { + let stat : u32 = match be_u32(r.prog_data) as IResult<&[u8],_> { + Ok((_, stat)) => stat, + _ => 0 + }; + nfs_status = stat; + } + SCLogDebug!("NFSv2: REPLY {} to procedure {} blob size {}", + r.hdr.xid, xidmap.procedure, r.prog_data.len()); + + self.mark_response_tx_done(r.hdr.xid, r.reply_state, nfs_status, &resp_handle); + } +} diff --git a/rust/src/nfs/nfs2_records.rs b/rust/src/nfs/nfs2_records.rs new file mode 100644 index 0000000..d8fe84f --- /dev/null +++ b/rust/src/nfs/nfs2_records.rs @@ -0,0 +1,235 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for NFSv2 records + +use crate::nfs::nfs_records::*; +use nom7::bytes::streaming::take; +use nom7::combinator::{rest, cond}; +use nom7::number::streaming::be_u32; +use nom7::IResult; + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs2Handle<'a> { + pub value: &'a[u8], +} + +pub fn parse_nfs2_handle(i: &[u8]) -> IResult<&[u8], Nfs2Handle> { + let (i, value) = take(32_usize)(i)?; + Ok((i, Nfs2Handle { value })) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs2RequestLookup<'a> { + pub handle: Nfs2Handle<'a>, + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs2_request_lookup(i: &[u8]) -> IResult<&[u8], Nfs2RequestLookup> { + let (i, handle) = parse_nfs2_handle(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name_contents) = take(name_len as usize)(i)?; + let (i, _name_padding) = rest(i)?; + let req = Nfs2RequestLookup { + handle, + name_vec: name_contents.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs2RequestRead<'a> { + pub handle: Nfs2Handle<'a>, + pub offset: u32, +} + +pub fn parse_nfs2_request_read(i: &[u8]) -> IResult<&[u8], Nfs2RequestRead> { + let (i, handle) = parse_nfs2_handle(i)?; + let (i, offset) = be_u32(i)?; + let (i, _count) = be_u32(i)?; + let req = Nfs2RequestRead { handle, offset }; + Ok((i, req)) +} + +pub fn parse_nfs2_reply_read(i: &[u8]) -> IResult<&[u8], NfsReplyRead> { + let (i, status) = be_u32(i)?; + let (i, attr_blob) = take(68_usize)(i)?; + let (i, data_len) = be_u32(i)?; + let (i, data_contents) = take(data_len)(i)?; + let fill_bytes = 4 - (data_len % 4); + let (i, _) = cond(fill_bytes != 0, take(fill_bytes))(i)?; + let reply = NfsReplyRead { + status, + attr_follows: 1, + attr_blob, + count: data_len, + eof: false, + data_len, + data: data_contents, + }; + Ok((i, reply)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs2Attributes<> { + pub atype: u32, + pub asize: u32, +} + +pub fn parse_nfs2_attribs(i: &[u8]) -> IResult<&[u8], Nfs2Attributes> { + let (i, atype) = be_u32(i)?; + let (i, _blob1) = take(16_usize)(i)?; + let (i, asize) = be_u32(i)?; + let (i, _blob2) = take(44_usize)(i)?; + let attrs = Nfs2Attributes { atype, asize }; + Ok((i, attrs)) +} + +#[cfg(test)] +mod tests { + use crate::nfs::nfs2_records::*; + + #[test] + fn test_nfs2_handle() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29 + ]; + + let result = parse_nfs2_handle(buf).unwrap(); + match result { + (r, res) => { + assert_eq!(r.len(), 0); + assert_eq!(res.value, buf); + } + } + } + + #[test] + fn test_nfs2_request_lookup() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x02, 0x61, 0x6d, 0x00, 0x00, /*name*/ + ]; + + let (_, handle) = parse_nfs2_handle(buf).unwrap(); + assert_eq!(handle.value, &buf[..32]); + + let result = parse_nfs2_request_lookup(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, handle); + assert_eq!(request.name_vec, b"am".to_vec()); + } + } + } + + #[test] + fn test_nfs2_request_read() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*file_handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, /*offset*/ + 0x00, 0x00, 0x20, 0x00, /*count*/ + 0x00, 0x00, 0x20, 0x00, /*_total_count*/ + ]; + + let (_, handle) = parse_nfs2_handle(buf).unwrap(); + assert_eq!(handle.value, &buf[..32]); + + let result = parse_nfs2_request_read(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 4); + assert_eq!(request.handle, handle); + assert_eq!(request.offset, 0); + } + } + } + + #[test] + fn test_nfs2_reply_read() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*Status: NFS_OK - (0)*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x81, 0xa4, /*attr_blob*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x10, 0x85, + 0x00, 0x00, 0xb2, 0x5d, 0x38, 0x47, 0x75, 0xea, + 0x00, 0x0b, 0x71, 0xb0, 0x38, 0x47, 0x71, 0xc4, + 0x00, 0x08, 0xb2, 0x90, 0x38, 0x47, 0x75, 0xea, + 0x00, 0x09, 0x00, 0xb0, + 0x00, 0x00, 0x00, 0x0b, /*data_len*/ + 0x74, 0x68, 0x65, 0x20, 0x62, 0x20, 0x66, 0x69, /*data_contents: ("the b file")*/ + 0x6c, 0x65, 0x0a, + 0x00, /*_data_padding*/ + ]; + + let result = parse_nfs2_reply_read(buf).unwrap(); + match result { + (r, response) => { + assert_eq!(r.len(), 0); + assert_eq!(response.status, 0); + assert_eq!(response.attr_follows, 1); + assert_eq!(response.attr_blob.len(), 68); + assert_eq!(response.count, response.data_len); + assert!(!response.eof); + assert_eq!(response.data_len, 11); + assert_eq!(response.data, &buf[76..87]); + } + } + } + + #[test] + fn test_nfs2_attributes() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x01, /*Type: Regular File (1)*/ + 0x00, 0x00, 0x81, 0xa4, 0x00, 0x00, 0x00, 0x01, /*attr: _blob1*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, /*size: 0*/ + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, /*attr: _blob2*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x85, + 0x00, 0x00, 0xa3, 0xe7, 0x38, 0x47, 0x75, 0xea, + 0x00, 0x08, 0x16, 0x50, 0x38, 0x47, 0x75, 0xea, + 0x00, 0x08, 0x16, 0x50, 0x38, 0x47, 0x75, 0xea, + 0x00, 0x08, 0x16, 0x50 + ]; + + let result = parse_nfs2_attribs(buf).unwrap(); + match result { + (r, res) => { + assert_eq!(r.len(), 0); + assert_eq!(res.atype, 1); + assert_eq!(res.asize, 0); + } + } + } +} diff --git a/rust/src/nfs/nfs3.rs b/rust/src/nfs/nfs3.rs new file mode 100644 index 0000000..032751f --- /dev/null +++ b/rust/src/nfs/nfs3.rs @@ -0,0 +1,274 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Victor Julien + +use crate::core::*; + +use crate::nfs::nfs::*; +use crate::nfs::types::*; +use crate::nfs::rpc_records::*; +use crate::nfs::nfs3_records::*; + +use nom7::IResult; +use nom7::number::streaming::be_u32; + +impl NFSState { + /// complete NFS3 request record + pub fn process_request_record_v3(&mut self, r: &RpcPacket) { + SCLogDebug!("REQUEST {} procedure {} ({}) blob size {}", + r.hdr.xid, r.procedure, self.requestmap.len(), r.prog_data.len()); + + let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); + let mut aux_file_name = Vec::new(); + + if self.nfs_version == 0 { + self.nfs_version = r.progver as u16; + } + + if r.procedure == NFSPROC3_LOOKUP { + self.process_request_record_lookup(r, &mut xidmap); + + } else if r.procedure == NFSPROC3_ACCESS { + if let Ok((_, rd)) = parse_nfs3_request_access(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_GETATTR { + if let Ok((_, rd)) = parse_nfs3_request_getattr(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_READDIRPLUS { + if let Ok((_, rd)) = parse_nfs3_request_readdirplus(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_READ { + if let Ok((_, rd)) = parse_nfs3_request_read(r.prog_data) { + xidmap.chunk_offset = rd.offset; + xidmap.file_handle = rd.handle.value.to_vec(); + self.xidmap_handle2name(&mut xidmap); + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_WRITE { + if let Ok((_, rd)) = parse_nfs3_request_write(r.prog_data, true) { + self.process_write_record(r, &rd); + } else { + self.set_event(NFSEvent::MalformedData); + } + } else if r.procedure == NFSPROC3_CREATE { + if let Ok((_, rd)) = parse_nfs3_request_create(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + xidmap.file_name = rd.name_vec; + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_REMOVE { + if let Ok((_, rd)) = parse_nfs3_request_remove(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + xidmap.file_name = rd.name_vec; + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_RENAME { + if let Ok((_, rd)) = parse_nfs3_request_rename(r.prog_data) { + xidmap.file_handle = rd.from_handle.value.to_vec(); + xidmap.file_name = rd.from_name_vec; + aux_file_name = rd.to_name_vec; + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_MKDIR { + if let Ok((_, rd)) = parse_nfs3_request_mkdir(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + xidmap.file_name = rd.name_vec; + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_RMDIR { + if let Ok((_, rd)) = parse_nfs3_request_rmdir(r.prog_data) { + xidmap.file_handle = rd.handle.value.to_vec(); + xidmap.file_name = rd.name_vec; + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if r.procedure == NFSPROC3_COMMIT { + SCLogDebug!("COMMIT, closing shop"); + if let Ok((_, rd)) = parse_nfs3_request_commit(r.prog_data) { + let file_handle = rd.handle.value.to_vec(); + if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.chunk_count += 1; + tdf.file_additional_procs.push(NFSPROC3_COMMIT); + filetracker_close(&mut tdf.file_tracker); + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.request_done = true; + tx.is_file_closed = true; + } + } + } else { + self.set_event(NFSEvent::MalformedData); + }; + } + + if !(r.procedure == NFSPROC3_COMMIT || // commit handled separately + r.procedure == NFSPROC3_WRITE || // write handled in file tx + r.procedure == NFSPROC3_READ) // read handled in file tx at reply + { + let mut tx = self.new_tx(); + tx.xid = r.hdr.xid; + tx.procedure = r.procedure; + tx.request_done = true; + tx.file_name = xidmap.file_name.to_vec(); + tx.nfs_version = r.progver as u16; + tx.file_handle = xidmap.file_handle.to_vec(); + + if r.procedure == NFSPROC3_RENAME { + tx.type_data = Some(NFSTransactionTypeData::RENAME(aux_file_name)); + } + + tx.auth_type = r.creds_flavor; + #[allow(clippy::single_match)] + match r.creds { + RpcRequestCreds::Unix(ref u) => { + tx.request_machine_name = u.machine_name_buf.to_vec(); + tx.request_uid = u.uid; + tx.request_gid = u.gid; + }, + _ => { }, + } + SCLogDebug!("TX created: ID {} XID {} PROCEDURE {}", + tx.id, tx.xid, tx.procedure); + self.transactions.push(tx); + + } else if r.procedure == NFSPROC3_READ { + + let found = self.get_file_tx_by_handle(&xidmap.file_handle, Direction::ToClient).is_some(); + if !found { + let tx = self.new_file_tx(&xidmap.file_handle, &xidmap.file_name, Direction::ToClient); + tx.procedure = NFSPROC3_READ; + tx.xid = r.hdr.xid; + tx.is_first = true; + tx.nfs_version = r.progver as u16; + + tx.auth_type = r.creds_flavor; + if let RpcRequestCreds::Unix(ref u) = r.creds { + tx.request_machine_name = u.machine_name_buf.to_vec(); + tx.request_uid = u.uid; + tx.request_gid = u.gid; + } + } + } + + self.requestmap.insert(r.hdr.xid, xidmap); + } + + pub fn process_reply_record_v3(&mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap) { + let mut nfs_status = 0; + let mut resp_handle = Vec::new(); + + if xidmap.procedure == NFSPROC3_LOOKUP { + if let Ok((_, rd)) = parse_nfs3_response_lookup(r.prog_data) { + SCLogDebug!("LOOKUP: {:?}", rd); + SCLogDebug!("RESPONSE LOOKUP file_name {:?}", xidmap.file_name); + + nfs_status = rd.status; + + SCLogDebug!("LOOKUP handle {:?}", rd.handle); + self.namemap.insert(rd.handle.value.to_vec(), xidmap.file_name.to_vec()); + resp_handle = rd.handle.value.to_vec(); + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if xidmap.procedure == NFSPROC3_CREATE { + if let Ok((_, rd)) = parse_nfs3_response_create(r.prog_data) { + SCLogDebug!("nfs3_create_record: {:?}", rd); + SCLogDebug!("RESPONSE CREATE file_name {:?}", xidmap.file_name); + nfs_status = rd.status; + + if let Some(h) = rd.handle { + SCLogDebug!("handle {:?}", h); + self.namemap.insert(h.value.to_vec(), xidmap.file_name.to_vec()); + resp_handle = h.value.to_vec(); + } + } else { + self.set_event(NFSEvent::MalformedData); + }; + } else if xidmap.procedure == NFSPROC3_READ { + if let Ok((_, rd)) = parse_nfs3_reply_read(r.prog_data, true) { + self.process_read_record(r, &rd, Some(xidmap)); + nfs_status = rd.status; + } else { + self.set_event(NFSEvent::MalformedData); + } + } else if xidmap.procedure == NFSPROC3_READDIRPLUS { + if let Ok((_, rd)) = parse_nfs3_response_readdirplus(r.prog_data) { + nfs_status = rd.status; + + // cut off final eof field + let d = if rd.data.len() >= 4 { + &rd.data[..rd.data.len()-4_usize] + } else { + rd.data + }; + + // store all handle/filename mappings + if let Ok((_, ref entries)) = many0_nfs3_response_readdirplus_entries(d) { + SCLogDebug!("READDIRPLUS ENTRIES reply {:?}", entries); + for ce in entries { + SCLogDebug!("ce {:?}", ce); + if let Some(ref e) = ce.entry { + SCLogDebug!("e {:?}", e); + if let Some(ref h) = e.handle { + SCLogDebug!("h {:?}", h); + self.namemap.insert(h.value.to_vec(), + e.name_vec.to_vec()); + } + } + } + } else { + self.set_event(NFSEvent::MalformedData); + } + } else { + self.set_event(NFSEvent::MalformedData); + } + } + // for all other record types only parse the status + else { + let stat : u32 = match be_u32(r.prog_data) as IResult<&[u8],_> { + Ok((_, stat)) => stat, + _ => 0 + }; + nfs_status = stat; + } + SCLogDebug!("REPLY {} to procedure {} blob size {}", + r.hdr.xid, xidmap.procedure, r.prog_data.len()); + + if xidmap.procedure != NFSPROC3_READ { + self.mark_response_tx_done(r.hdr.xid, r.reply_state, nfs_status, &resp_handle); + } + } +} diff --git a/rust/src/nfs/nfs3_records.rs b/rust/src/nfs/nfs3_records.rs new file mode 100644 index 0000000..952b367 --- /dev/null +++ b/rust/src/nfs/nfs3_records.rs @@ -0,0 +1,1000 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for RPC & NFSv3 + +use std::cmp; +use crate::nfs::nfs_records::*; +use nom7::bytes::streaming::take; +use nom7::combinator::{complete, cond, rest, verify}; +use nom7::multi::{length_data, many0}; +use nom7::number::streaming::{be_u32, be_u64}; +use nom7::IResult; + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3Handle<'a> { + pub len: u32, + pub value: &'a [u8], +} + +pub fn parse_nfs3_handle(i: &[u8]) -> IResult<&[u8], Nfs3Handle> { + let (i, len) = be_u32(i)?; + let (i, value) = take(len as usize)(i)?; + let handle = Nfs3Handle { len, value }; + Ok((i, handle)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3ReplyCreate<'a> { + pub status: u32, + pub handle: Option<Nfs3Handle<'a>>, +} + +pub fn parse_nfs3_response_create(i: &[u8]) -> IResult<&[u8], Nfs3ReplyCreate> { + let (i, status) = be_u32(i)?; + let (i, handle_has_value) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, handle) = cond(handle_has_value == 1, parse_nfs3_handle)(i)?; + let reply = Nfs3ReplyCreate { status, handle }; + Ok((i, reply)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3ReplyLookup<'a> { + pub status: u32, + pub handle: Nfs3Handle<'a>, +} + +pub fn parse_nfs3_response_lookup(i: &[u8]) -> IResult<&[u8], Nfs3ReplyLookup> { + let (i, status) = be_u32(i)?; + let (i, handle) = parse_nfs3_handle(i)?; + let reply = Nfs3ReplyLookup { status, handle }; + Ok((i, reply)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestCreate<'a> { + pub handle: Nfs3Handle<'a>, + pub name_len: u32, + pub create_mode: u32, + pub verifier: &'a [u8], + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_create(i: &[u8]) -> IResult<&[u8], Nfs3RequestCreate> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name) = take(name_len as usize)(i)?; + let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?; + let (i, create_mode) = be_u32(i)?; + let (i, verifier) = rest(i)?; + let req = Nfs3RequestCreate { + handle, + name_len, + create_mode, + verifier, + name_vec: name.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestRemove<'a> { + pub handle: Nfs3Handle<'a>, + pub name_len: u32, + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_remove(i: &[u8]) -> IResult<&[u8], Nfs3RequestRemove> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name) = take(name_len as usize)(i)?; + let (i, _fill_bytes) = rest(i)?; + let req = Nfs3RequestRemove { + handle, + name_len, + name_vec: name.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestRmdir<'a> { + pub handle: Nfs3Handle<'a>, + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_rmdir(i: &[u8]) -> IResult<&[u8], Nfs3RequestRmdir> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name) = take(name_len as usize)(i)?; + let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?; + let req = Nfs3RequestRmdir { + handle, + name_vec: name.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestMkdir<'a> { + pub handle: Nfs3Handle<'a>, + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_mkdir(i: &[u8]) -> IResult<&[u8], Nfs3RequestMkdir> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name) = take(name_len as usize)(i)?; + let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?; + let (i, _attributes) = rest(i)?; + let req = Nfs3RequestMkdir { + handle, + name_vec: name.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestRename<'a> { + pub from_handle: Nfs3Handle<'a>, + pub from_name_vec: Vec<u8>, + pub to_handle: Nfs3Handle<'a>, + pub to_name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_rename(i: &[u8]) -> IResult<&[u8], Nfs3RequestRename> { + let (i, from_handle) = parse_nfs3_handle(i)?; + let (i, from_name_len) = be_u32(i)?; + let (i, from_name) = take(from_name_len as usize)(i)?; + let (i, _from_fill_bytes) = cond(from_name_len % 4 != 0, take(4 - (from_name_len % 4)))(i)?; + + let (i, to_handle) = parse_nfs3_handle(i)?; + let (i, to_name_len) = be_u32(i)?; + let (i, to_name) = take(to_name_len as usize)(i)?; + let (i, _from_fill_bytes) = rest(i)?; + let req = Nfs3RequestRename { + from_handle, + from_name_vec: from_name.to_vec(), + to_handle, + to_name_vec: to_name.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestGetAttr<'a> { + pub handle: Nfs3Handle<'a>, +} + +pub fn parse_nfs3_request_getattr(i: &[u8]) -> IResult<&[u8], Nfs3RequestGetAttr> { + let (i, handle) = parse_nfs3_handle(i)?; + Ok((i, Nfs3RequestGetAttr { handle })) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestAccess<'a> { + pub handle: Nfs3Handle<'a>, + pub check_access: u32, +} + +pub fn parse_nfs3_request_access(i: &[u8]) -> IResult<&[u8], Nfs3RequestAccess> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, check_access) = be_u32(i)?; + let req = Nfs3RequestAccess { + handle, + check_access, + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestCommit<'a> { + pub handle: Nfs3Handle<'a>, +} + +pub fn parse_nfs3_request_commit(i: &[u8]) -> IResult<&[u8], Nfs3RequestCommit> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, _offset) = be_u64(i)?; + let (i, _count) = be_u32(i)?; + Ok((i, Nfs3RequestCommit { handle })) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestRead<'a> { + pub handle: Nfs3Handle<'a>, + pub offset: u64, +} + +pub fn parse_nfs3_request_read(i: &[u8]) -> IResult<&[u8], Nfs3RequestRead> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, offset) = be_u64(i)?; + let (i, _count) = be_u32(i)?; + Ok((i, Nfs3RequestRead { handle, offset })) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestLookup<'a> { + pub handle: Nfs3Handle<'a>, + pub name_vec: Vec<u8>, +} + +pub fn parse_nfs3_request_lookup(i: &[u8]) -> IResult<&[u8], Nfs3RequestLookup> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, name_contents) = length_data(be_u32)(i)?; + let (i, _name_padding) = rest(i)?; + let req = Nfs3RequestLookup { + handle, + name_vec: name_contents.to_vec(), + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3ResponseReaddirplusEntryC<'a> { + pub name_vec: Vec<u8>, + pub handle: Option<Nfs3Handle<'a>>, +} + +pub fn parse_nfs3_response_readdirplus_entry( + i: &[u8], +) -> IResult<&[u8], Nfs3ResponseReaddirplusEntryC> { + let (i, _file_id) = be_u64(i)?; + let (i, name_len) = be_u32(i)?; + let (i, name_contents) = take(name_len as usize)(i)?; + let (i, _fill_bytes) = cond(name_len % 4 != 0, take(4 - (name_len % 4)))(i)?; + let (i, _cookie) = take(8_usize)(i)?; + let (i, attr_value_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, _attr) = cond(attr_value_follows == 1, take(84_usize))(i)?; + let (i, handle_value_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, handle) = cond(handle_value_follows == 1, parse_nfs3_handle)(i)?; + let resp = Nfs3ResponseReaddirplusEntryC { + name_vec: name_contents.to_vec(), + handle, + }; + Ok((i, resp)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3ResponseReaddirplusEntry<'a> { + pub entry: Option<Nfs3ResponseReaddirplusEntryC<'a>>, +} + +pub fn parse_nfs3_response_readdirplus_entry_cond( + i: &[u8], +) -> IResult<&[u8], Nfs3ResponseReaddirplusEntry> { + let (i, value_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, entry) = cond(value_follows == 1, parse_nfs3_response_readdirplus_entry)(i)?; + Ok((i, Nfs3ResponseReaddirplusEntry { entry })) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3ResponseReaddirplus<'a> { + pub status: u32, + pub data: &'a [u8], +} + +pub fn parse_nfs3_response_readdirplus(i: &[u8]) -> IResult<&[u8], Nfs3ResponseReaddirplus> { + let (i, status) = be_u32(i)?; + let (i, dir_attr_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, _dir_attr) = cond(dir_attr_follows == 1, take(84_usize))(i)?; + let (i, _verifier) = be_u64(i)?; + let (i, data) = rest(i)?; + let resp = Nfs3ResponseReaddirplus { status, data }; + Ok((i, resp)) +} + +pub(crate) fn many0_nfs3_response_readdirplus_entries( + input: &[u8], +) -> IResult<&[u8], Vec<Nfs3ResponseReaddirplusEntry>> { + many0(complete(parse_nfs3_response_readdirplus_entry_cond))(input) +} + + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestReaddirplus<'a> { + pub handle: Nfs3Handle<'a>, + pub cookie: u64, + pub verifier: &'a [u8], + pub dircount: u32, + pub maxcount: u32, +} + +pub fn parse_nfs3_request_readdirplus(i: &[u8]) -> IResult<&[u8], Nfs3RequestReaddirplus> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, cookie) = be_u64(i)?; + let (i, verifier) = take(8_usize)(i)?; + let (i, dircount) = be_u32(i)?; + let (i, maxcount) = be_u32(i)?; + let req = Nfs3RequestReaddirplus { + handle, + cookie, + verifier, + dircount, + maxcount, + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs3RequestWrite<'a> { + pub handle: Nfs3Handle<'a>, + pub offset: u64, + pub count: u32, + pub stable: u32, + pub file_len: u32, + pub file_data: &'a [u8], +} + +/// Complete data expected +fn parse_nfs3_data_complete(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> { + let (i, file_data) = take(file_len)(i)?; + let (i, _) = cond(fill_bytes > 0, take(fill_bytes))(i)?; + Ok((i, file_data)) +} + +/// Partial data. We have all file_len, but need to consider fill_bytes +fn parse_nfs3_data_partial(i: &[u8], file_len: usize, fill_bytes: usize) -> IResult<&[u8], &[u8]> { + let (i, file_data) = take(file_len)(i)?; + let fill_bytes = cmp::min(fill_bytes, i.len()); + let (i, _) = cond(fill_bytes > 0, take(fill_bytes))(i)?; + Ok((i, file_data)) +} + +/// Parse WRITE record. Consider 3 cases: +/// 1. we have the complete RPC data +/// 2. we have incomplete data but enough for all file data (partial fill bytes) +/// 3. we have incomplete file data +pub fn parse_nfs3_request_write(i: &[u8], complete: bool) -> IResult<&[u8], Nfs3RequestWrite> { + let (i, handle) = parse_nfs3_handle(i)?; + let (i, offset) = be_u64(i)?; + let (i, count) = be_u32(i)?; + let (i, stable) = verify(be_u32, |&v| v <= 2)(i)?; + let (i, file_len) = verify(be_u32, |&v| v <= count)(i)?; + let fill_bytes = if file_len % 4 != 0 { 4 - file_len % 4 } else { 0 }; + // Handle the various file data parsing logics + let (i, file_data) = if complete { + parse_nfs3_data_complete(i, file_len as usize, fill_bytes as usize)? + } else if i.len() >= file_len as usize { + parse_nfs3_data_partial(i, file_len as usize, fill_bytes as usize)? + } else { + rest(i)? + }; + let req = Nfs3RequestWrite { + handle, + offset, + count, + stable, + file_len, + file_data, + }; + Ok((i, req)) +} + +pub fn parse_nfs3_reply_read(i: &[u8], complete: bool) -> IResult<&[u8], NfsReplyRead> { + let (i, status) = be_u32(i)?; + let (i, attr_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, attr_blob) = take(84_usize)(i)?; // fixed size? + let (i, count) = be_u32(i)?; + let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, data_len) = verify(be_u32, |&v| v <= count)(i)?; + let fill_bytes = if data_len % 4 != 0 { 4 - data_len % 4 } else { 0 }; + // Handle the various file data parsing logics + let (i, data) = if complete { + parse_nfs3_data_complete(i, data_len as usize, fill_bytes as usize)? + } else if i.len() >= data_len as usize { + parse_nfs3_data_partial(i, data_len as usize, fill_bytes as usize)? + } else { + rest(i)? + }; + let reply = NfsReplyRead { + status, + attr_follows, + attr_blob, + count, + eof: eof != 0, + data_len, + data, + }; + Ok((i, reply)) +} + +#[cfg(test)] +mod tests { + use crate::nfs::nfs3_records::*; + + #[test] + fn test_nfs3_response_create() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/ + 0x00, 0x00, 0x00, 0x01, /*handle_follows: (1)*/ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[8..]).unwrap(); + + let (_, response) = parse_nfs3_response_create(buf).unwrap(); + assert_eq!(response.status, 0); + assert_eq!(response.handle, Some(expected_handle)); + } + + #[test] + fn test_nfs3_response_lookup() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xec, + 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[4..]).unwrap(); + + let (_, response) = parse_nfs3_response_lookup(buf).unwrap(); + assert_eq!(response.status, 0); + assert_eq!(response.handle, expected_handle); + } + + #[test] + fn test_nfs3_request_create() { + #[rustfmt::skip] + let buf: &[u8] = &[ + // [handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xe7, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [name] + 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/ + 0x68, /*name_contents: (h)*/ + 0x00, 0x00, 0x00, /*_fill_bytes*/ + 0x00, 0x00, 0x00, 0x00, /*create_mode: UNCHECKED (0)*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xa4, /*verifier*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_create(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.name_len, 1); + assert_eq!(request.create_mode, 0); + assert_eq!(request.verifier.len(), 44); + assert_eq!(request.name_vec, br#"h"#.to_vec()); + } + } + } + + #[test] + fn test_nfs3_request_remove() { + #[rustfmt::skip] + let buf: &[u8] = &[ + // [handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa3, 0xe7, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [name] + 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/ + 0x68, /*name_contents: (h)*/ + 0x00, 0x00, 0x00, /*_fill_bytes*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_remove(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.name_len, 1); + assert_eq!(request.name_vec, br#"h"#.to_vec()); + } + } + } + + #[test] + fn test_nfs3_request_rmdir() { + #[rustfmt::skip] + let buf: &[u8] = &[ + //[handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [name] + 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/ + 0x64, /*name_contents: (d)*/ + 0x00, 0x00, 0x00, /*_fill_bytes*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_rmdir(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.name_vec, br#"d"#.to_vec()); + } + } + } + + #[test] + fn test_nfs3_request_mkdir() { + #[rustfmt::skip] + let buf: &[u8] = &[ + // [handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [name] + 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/ + 0x64, /*name_contents: (d)*/ + 0x00, 0x00, 0x00, /*_fill_bytes*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xed, /*attributes*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_mkdir(buf).unwrap(); + match result { + (_r, request) => { + assert_eq!(request.handle, expected_handle); + assert_eq!(request.name_vec, br#"d"#.to_vec()); + } + } + } + + #[test] + fn test_nfs3_request_rename() { + #[rustfmt::skip] + let buf: &[u8] = &[ + // [from_handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [from_name] + 0x00, 0x00, 0x00, 0x01, /*name_len: (1)*/ + 0x61, /*name: (a)*/ + 0x00, 0x00, 0x00, /*_fill_bytes*/ + // [to_handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [to_name] + 0x00, 0x00, 0x00, 0x02, /*name_len: (2)*/ + 0x61, 0x6d, /*name: (am)*/ + 0x00, 0x00, /*_fill_bytes*/ + ]; + + let (_, expected_from_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + let (_, expected_to_handle) = parse_nfs3_handle(&buf[44..80]).unwrap(); + + let result = parse_nfs3_request_rename(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + + assert_eq!(request.from_handle, expected_from_handle); + assert_eq!(request.from_name_vec, br#"a"#.to_vec()); + + assert_eq!(request.to_handle, expected_to_handle); + assert_eq!(request.to_name_vec, br#"am"#.to_vec()); + } + } + } + + #[test] + fn test_nfs3_request_getattr() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + ]; + + let (_, expected_handle) = parse_nfs3_handle(buf).unwrap(); + + let result = parse_nfs3_request_getattr(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + } + } + } + + #[test] + fn test_nfs3_request_access() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x0c, /*check_access: (12)*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_access(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.check_access, 12); + } + } + } + + #[test] + fn test_nfs3_request_commit() { + + // packet_bytes -- used [READ Call] message digest + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/ + 0x00, 0x00, 0x40, 0x00, /*count:*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_commit(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + } + } + } + + #[test] + fn test_nfs3_request_read() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5d, + 0x00, 0x00, 0x00, 0x2a, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/ + 0x00, 0x00, 0x40, 0x00, /*_count*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_read(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.offset, 0); + } + } + } + + #[test] + fn test_nfs3_request_lookup() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + // [handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xb2, 0x5a, + 0x00, 0x00, 0x00, 0x29, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + // [name] + 0x00, 0x00, 0x00, 0x03, /*name_len: (3)*/ + 0x62, 0x6c, 0x6e, /*name: (bln)*/ + 0x00, /*_fill_bytes*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_lookup(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.name_vec, br#"bln"#); + } + } + } + + #[test] + fn test_nfs3_response_readdirplus() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x01, /*dir_attr_follows*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x41, 0xc0, /*_dir_attr*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x63, 0xc3, 0x9d, 0x1a, + 0x66, 0xf3, 0x85, 0x5e, 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x66, 0x00, 0x03, 0x59, 0x39, 0x5a, 0x3a, + 0x18, 0x13, 0x9c, 0xb2, 0x55, 0xe1, 0x59, 0xd4, + 0x0e, 0xa0, 0xc0, 0x41, 0x55, 0xe1, 0x59, 0xd4, + 0x0e, 0xa0, 0xc0, 0x41, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/ + // [data] + 0x00, 0x00, 0x00, 0x01, /*value_follows*/ + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x01, /*entry0*/ + 0x00, 0x00, 0x00, 0x02, 0x2e, 0x2e, 0x00, 0x00, /*name_contents: \2e\2e */ + 0x00, 0x00, 0x00, 0x00, 0x7c, 0x1b, 0xc6, 0xaf, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x05, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x01, + 0x59, 0x38, 0xa1, 0xa1, 0x04, 0x29, 0xd9, 0x59, + 0x4e, 0xbf, 0xf1, 0x51, 0x09, 0x2c, 0xa1, 0xda, + 0x4e, 0xbf, 0xf1, 0x51, 0x09, 0x2c, 0xa1, 0xda, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, + 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/ + 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66, + 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x66, 0x0c, + 0x4a, 0xff, 0x6b, 0x99, + 0x00, 0x00, 0x00, 0x01, /*value_follows*/ + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, /*entry1*/ + 0x00, 0x00, 0x00, 0x01, 0x2e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, + 0x59, 0x39, 0x5a, 0x3a, 0x18, 0x13, 0x9c, 0xb2, + 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41, + 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, + 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/ + 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66, + 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c, + 0x4c, 0xff, 0x6b, 0x99, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + ]; + + let data_buf = &buf[100..]; + + let (_, response) = parse_nfs3_response_readdirplus(buf).unwrap(); + assert_eq!(response.status, 0); + assert_eq!(response.data, data_buf); + + // test for multiple entries + let entry0_buf = &data_buf[4..160]; + let entry1_buf = &data_buf[164..320]; + + let (_, entry0) = parse_nfs3_response_readdirplus_entry(entry0_buf).unwrap(); + let (_, entry1) = parse_nfs3_response_readdirplus_entry(entry1_buf).unwrap(); + + let response = many0_nfs3_response_readdirplus_entries(data_buf).unwrap(); + match response { + (r, entries) => { + assert_eq!(r.len(), 4); + assert_eq!(entries[0], Nfs3ResponseReaddirplusEntry { entry: Some(entry0) }); + assert_eq!(entries[1], Nfs3ResponseReaddirplusEntry { entry: Some(entry1) }); + } + } + } + + #[test] + fn test_nfs3_response_readdirplus_entry() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x01, /*value_follows*/ + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, /*entry*/ + 0x00, 0x00, 0x00, 0x01, 0x2e, 0x00, 0x00, 0x00, /*name_contents: 2e*/ + 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x41, 0xc0, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0xc3, 0x9d, 0x1a, 0x66, 0xf3, 0x85, 0x5e, + 0x00, 0x00, 0x00, 0x00, 0x0c, 0x66, 0x00, 0x03, + 0x59, 0x39, 0x5a, 0x3a, 0x18, 0x13, 0x9c, 0xb2, + 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41, + 0x55, 0xe1, 0x59, 0xd4, 0x0e, 0xa0, 0xc0, 0x41, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, + 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/ + 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66, + 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c, + 0x4c, 0xff, 0x6b, 0x99, + ]; + + let (_, entry_handle) = parse_nfs3_handle(&buf[120..]).unwrap(); + assert_eq!(entry_handle.len, 36); + assert_eq!(entry_handle.value, &buf[124..]); + + let (_, response) = parse_nfs3_response_readdirplus_entry_cond(buf).unwrap(); + match response { + Nfs3ResponseReaddirplusEntry { entry: Some(entry_c) } => { + assert_eq!(entry_c.name_vec, ".".as_bytes()); + assert_eq!(entry_c.handle, Some(entry_handle)); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs3_request_readdirplus() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x24, /*handle_len*/ + 0x01, 0x00, 0x07, 0x01, 0x01, 0x00, 0xd4, 0x09, /*handle*/ + 0x00, 0x00, 0x00, 0x00, 0x5e, 0x85, 0xf3, 0x66, + 0x1a, 0x9d, 0xc3, 0x63, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x66, 0x0c, + 0x4c, 0xff, 0x6b, 0x99, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*cookie*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*verifier*/ + 0x00, 0x00, 0x02, 0x00, /*dircount*/ + 0x00, 0x00, 0x10, 0x00, /*maxcount*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..40]).unwrap(); + assert_eq!(expected_handle.len, 36); + assert_eq!(expected_handle.value, &buf[4..40]); + + let result = parse_nfs3_request_readdirplus(buf).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.cookie, 0); + assert_eq!(request.verifier, "\0\0\0\0\0\0\0\0".as_bytes()); + assert_eq!(request.verifier.len(), 8); + assert_eq!(request.dircount, 512); + assert_eq!(request.maxcount, 4096); + } + } + } + + #[test] + fn test_nfs3_request_write() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + // [handle] + 0x00, 0x00, 0x00, 0x20, /*handle_len: (32)*/ + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x03, 0xe7, /*handle*/ + 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x54, + 0x00, 0x00, 0x00, 0x1b, 0x00, 0x0a, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5a, 0x00, 0x00, 0x00, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset: (0)*/ + 0x00, 0x00, 0x00, 0x11, /*count: (17)*/ + 0x00, 0x00, 0x00, 0x01, /*stable: <DATA_SYNC> (1)*/ + // [data] + 0x00, 0x00, 0x00, 0x11, /*file_len: (17)*/ + 0x68, 0x61, 0x6c, 0x6c, 0x6f, 0x0a, 0x74, 0x68, /*file_data: ("hallo\nthe b file\n")*/ + 0x65, 0x20, 0x62, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x0a, + 0x00, 0x00, 0x00, /*_data_padding*/ + ]; + + let (_, expected_handle) = parse_nfs3_handle(&buf[..36]).unwrap(); + + let result = parse_nfs3_request_write(buf, true).unwrap(); + match result { + (r, request) => { + assert_eq!(r.len(), 0); + assert_eq!(request.handle, expected_handle); + assert_eq!(request.offset, 0); + assert_eq!(request.count, 17); + assert_eq!(request.stable, 1); + assert_eq!(request.file_len, 17); + assert_eq!(request.file_data, "hallo\nthe b file\n".as_bytes()); + } + } + } + + #[test] + fn test_nfs3_reply_read() { + + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*Status: NFS3_OK (0)*/ + 0x00, 0x00, 0x00, 0x01, /*attributes_follows: (1)*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x81, 0xa4, /*attr_blob*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x10, 0x85, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xb2, 0x5d, 0x38, 0x47, 0x76, 0x25, + 0x23, 0xc3, 0x46, 0x00, 0x38, 0x47, 0x71, 0xc4, + 0x21, 0xf9, 0x82, 0x80, 0x38, 0x47, 0x76, 0x25, + 0x1e, 0x65, 0xfb, 0x81, + 0x00, 0x00, 0x00, 0x0b, /*count: (11)*/ + 0x00, 0x00, 0x00, 0x01, /*EOF: (true)*/ + // [data] + 0x00, 0x00, 0x00, 0x0b, /*data_len: (11)*/ + 0x74, 0x68, 0x65, 0x20, 0x62, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x0a, /*data: ("the b file\n")*/ + 0x00, /*_data_padding*/ + ]; + + let result = parse_nfs3_reply_read(buf, true).unwrap(); + match result { + (r, reply) => { + assert_eq!(r.len(), 0); + assert_eq!(reply.status, 0); + assert_eq!(reply.attr_follows, 1); + assert_eq!(reply.attr_blob.len(), 84); + assert_eq!(reply.count, 11); + assert!(reply.eof); + assert_eq!(reply.data_len, 11); + assert_eq!(reply.data, "the b file\n".as_bytes()); + } + } + } +} diff --git a/rust/src/nfs/nfs4.rs b/rust/src/nfs/nfs4.rs new file mode 100644 index 0000000..730e82b --- /dev/null +++ b/rust/src/nfs/nfs4.rs @@ -0,0 +1,425 @@ +/* Copyright (C) 2018-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Victor Julien + +use nom7::bytes::streaming::take; +use nom7::number::streaming::be_u32; +use nom7::{Err, IResult}; + +use crate::core::*; +use crate::nfs::nfs::*; +use crate::nfs::nfs4_records::*; +use crate::nfs::nfs_records::*; +use crate::nfs::rpc_records::*; +use crate::nfs::types::*; + +use crate::kerberos::{parse_kerberos5_request, Kerberos5Ticket, SecBlobError}; + +fn parse_req_gssapi(i: &[u8]) -> IResult<&[u8], Kerberos5Ticket, SecBlobError> { + let (i, len) = be_u32(i)?; + let (i, buf) = take(len as usize)(i)?; + let (_, ap) = parse_kerberos5_request(buf)?; + Ok((i, ap)) +} + +impl NFSState { + /* normal write: PUTFH (file handle), WRITE (write opts/data). File handle + * is not part of the write record itself so we pass it in here. */ + fn write_v4<'b>(&mut self, r: &RpcPacket<'b>, w: &Nfs4RequestWrite<'b>, fh: &'b [u8]) { + // for now assume that stable FILE_SYNC flags means a single chunk + let is_last = w.stable == 2; + SCLogDebug!("is_last {}", is_last); + + let mut fill_bytes = 0; + let pad = w.write_len % 4; + if pad != 0 { + fill_bytes = 4 - pad; + } + + // linux defines a max of 1mb. Allow several multiples. + if w.write_len == 0 || w.write_len > 16777216 { + return; + } + + let file_handle = fh.to_vec(); + let file_name = if let Some(name) = self.namemap.get(fh) { + SCLogDebug!("WRITE name {:?}", name); + name.to_vec() + } else { + SCLogDebug!("WRITE object {:?} not found", w.stateid.data); + Vec::new() + }; + + let found = match self.get_file_tx_by_handle(&file_handle, Direction::ToServer) { + Some(tx) => { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, w.data, w.offset, + w.write_len, fill_bytes as u8, is_last, &r.hdr.xid); + tdf.chunk_count += 1; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.response_done = true; + } + } + true + } + None => false, + }; + if !found { + let tx = self.new_file_tx(&file_handle, &file_name, Direction::ToServer); + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, w.data, w.offset, + w.write_len, fill_bytes as u8, is_last, &r.hdr.xid); + tx.procedure = NFSPROC4_WRITE; + tx.xid = r.hdr.xid; + tx.is_first = true; + tx.nfs_version = r.progver as u16; + if is_last { + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.request_done = true; + tx.is_file_closed = true; + } + } + } + self.ts_chunk_xid = r.hdr.xid; + debug_validate_bug_on!(w.data.len() as u32 > w.write_len); + self.ts_chunk_left = w.write_len - w.data.len() as u32; + } + + fn close_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) { + self.commit_v4(r, fh) + } + + fn commit_v4<'b>(&mut self, r: &RpcPacket<'b>, fh: &'b [u8]) { + SCLogDebug!("COMMIT, closing shop"); + + let file_handle = fh.to_vec(); + if let Some(tx) = self.get_file_tx_by_handle(&file_handle, Direction::ToServer) { + if let Some(NFSTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + tdf.file_last_xid = r.hdr.xid; + tx.is_last = true; + tx.request_done = true; + tx.is_file_closed = true; + } + } + } + + fn new_tx_v4( + &mut self, r: &RpcPacket, xidmap: &NFSRequestXidMap, procedure: u32, + _aux_opcodes: &[u32], + ) { + let mut tx = self.new_tx(); + tx.xid = r.hdr.xid; + tx.procedure = procedure; + tx.request_done = true; + tx.file_name = xidmap.file_name.to_vec(); + tx.nfs_version = r.progver as u16; + tx.file_handle = xidmap.file_handle.to_vec(); + + tx.auth_type = r.creds_flavor; + #[allow(clippy::single_match)] + match r.creds { + RpcRequestCreds::Unix(ref u) => { + tx.request_machine_name = u.machine_name_buf.to_vec(); + tx.request_uid = u.uid; + tx.request_gid = u.gid; + } + _ => {} + } + SCLogDebug!( + "NFSv4: TX created: ID {} XID {} PROCEDURE {}", + tx.id, + tx.xid, + tx.procedure + ); + self.transactions.push(tx); + } + + /* A normal READ request looks like: PUTFH (file handle) READ (read opts). + * We need the file handle for the READ. + */ + fn compound_request<'b>( + &mut self, r: &RpcPacket<'b>, cr: &Nfs4RequestCompoundRecord<'b>, + xidmap: &mut NFSRequestXidMap, + ) { + let mut last_putfh: Option<&'b [u8]> = None; + let mut main_opcode: u32 = 0; + let mut aux_opcodes: Vec<u32> = Vec::new(); + + for c in &cr.commands { + SCLogDebug!("c {:?}", c); + match *c { + Nfs4RequestContent::PutFH(ref rd) => { + last_putfh = Some(rd.value); + aux_opcodes.push(NFSPROC4_PUTFH); + } + Nfs4RequestContent::Read(ref rd) => { + SCLogDebug!("READv4: {:?}", rd); + if let Some(fh) = last_putfh { + xidmap.chunk_offset = rd.offset; + xidmap.file_handle = fh.to_vec(); + self.xidmap_handle2name(xidmap); + } + } + Nfs4RequestContent::Open(ref rd) => { + SCLogDebug!("OPENv4: {}", String::from_utf8_lossy(rd.filename)); + xidmap.file_name = rd.filename.to_vec(); + } + Nfs4RequestContent::Lookup(ref rd) => { + SCLogDebug!("LOOKUPv4: {}", String::from_utf8_lossy(rd.filename)); + xidmap.file_name = rd.filename.to_vec(); + } + Nfs4RequestContent::Write(ref rd) => { + SCLogDebug!("WRITEv4: {:?}", rd); + if let Some(fh) = last_putfh { + self.write_v4(r, rd, fh); + } + } + Nfs4RequestContent::Commit => { + SCLogDebug!("COMMITv4"); + if let Some(fh) = last_putfh { + self.commit_v4(r, fh); + } + } + Nfs4RequestContent::Close(ref _rd) => { + SCLogDebug!("CLOSEv4: {:?}", _rd); + if let Some(fh) = last_putfh { + self.close_v4(r, fh); + } + } + Nfs4RequestContent::Create(ref rd) => { + SCLogDebug!("CREATEv4: {:?}", rd); + if let Some(fh) = last_putfh { + xidmap.file_handle = fh.to_vec(); + } + xidmap.file_name = rd.filename.to_vec(); + main_opcode = NFSPROC4_CREATE; + } + Nfs4RequestContent::Remove(rd) => { + SCLogDebug!("REMOVEv4: {:?}", rd); + xidmap.file_name = rd.to_vec(); + main_opcode = NFSPROC4_REMOVE; + } + Nfs4RequestContent::SetClientId(ref _rd) => { + SCLogDebug!( + "SETCLIENTIDv4: client id {} r_netid {} r_addr {}", + String::from_utf8_lossy(_rd.client_id), + String::from_utf8_lossy(_rd.r_netid), + String::from_utf8_lossy(_rd.r_addr) + ); + } + _ => {} + } + } + + if main_opcode != 0 { + self.new_tx_v4(r, xidmap, main_opcode, &aux_opcodes); + } + } + + /// complete request record + pub fn process_request_record_v4(&mut self, r: &RpcPacket) { + SCLogDebug!( + "NFSv4 REQUEST {} procedure {} ({}) blob size {}", + r.hdr.xid, + r.procedure, + self.requestmap.len(), + r.prog_data.len() + ); + + let mut xidmap = NFSRequestXidMap::new(r.progver, r.procedure, 0); + + if r.procedure == NFSPROC4_NULL { + if let RpcRequestCreds::GssApi(ref creds) = r.creds { + if creds.procedure == 1 { + let _x = parse_req_gssapi(r.prog_data); + SCLogDebug!("RPCSEC_GSS_INIT {:?}", _x); + } + } + } else if r.procedure == NFSPROC4_COMPOUND { + let mut data = r.prog_data; + + if let RpcRequestCreds::GssApi(ref creds) = r.creds { + if creds.procedure == 0 && creds.service == 2 { + SCLogDebug!("GSS INTEGRITY: {:?}", creds); + match parse_rpc_gssapi_integrity(r.prog_data) { + Ok((_rem, rec)) => { + SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec); + data = rec.data; + // store proc and serv for the reply + xidmap.gssapi_proc = creds.procedure; + xidmap.gssapi_service = creds.service; + } + Err(Err::Incomplete(_n)) => { + SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n); + self.set_event(NFSEvent::MalformedData); + return; + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + SCLogDebug!( + "NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}", + _e + ); + self.set_event(NFSEvent::MalformedData); + return; + } + } + } + } + + match parse_nfs4_request_compound(data) { + Ok((_, rd)) => { + SCLogDebug!("NFSPROC4_COMPOUND: {:?}", rd); + self.compound_request(r, &rd, &mut xidmap); + } + Err(Err::Incomplete(_n)) => { + SCLogDebug!("NFSPROC4_COMPOUND: INCOMPLETE {:?}", _n); + self.set_event(NFSEvent::MalformedData); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + SCLogDebug!("NFSPROC4_COMPOUND: Parsing failed: {:?}", _e); + self.set_event(NFSEvent::MalformedData); + } + }; + } + + self.requestmap.insert(r.hdr.xid, xidmap); + } + + fn compound_response<'b>( + &mut self, r: &RpcReplyPacket<'b>, cr: &Nfs4ResponseCompoundRecord<'b>, + xidmap: &mut NFSRequestXidMap, + ) { + let mut insert_filename_with_getfh = false; + let mut main_opcode_status: u32 = 0; + let mut main_opcode_status_set: bool = false; + + for c in &cr.commands { + SCLogDebug!("c {:?}", c); + match *c { + Nfs4ResponseContent::ReadDir(_s, Some(ref rd)) => { + SCLogDebug!("READDIRv4: status {} eof {}", _s, rd.eof); + + #[allow(clippy::manual_flatten)] + for d in &rd.listing { + if let Some(_d) = d { + SCLogDebug!("READDIRv4: dir {}", String::from_utf8_lossy(_d.name)); + } + } + } + Nfs4ResponseContent::Remove(s) => { + SCLogDebug!("REMOVE4: status {}", s); + main_opcode_status = s; + main_opcode_status_set = true; + } + Nfs4ResponseContent::Create(s) => { + SCLogDebug!("CREATE4: status {}", s); + main_opcode_status = s; + main_opcode_status_set = true; + } + Nfs4ResponseContent::Read(s, Some(ref rd)) => { + SCLogDebug!( + "READ4: xidmap {:?} status {} data {}", + xidmap, + s, + rd.data.len() + ); + // convert record to generic read reply + let reply = NfsReplyRead { + status: s, + attr_follows: 0, + attr_blob: &[], + count: rd.count, + eof: rd.eof, + data_len: rd.data.len() as u32, + data: rd.data, + }; + self.process_read_record(r, &reply, Some(xidmap)); + } + Nfs4ResponseContent::Open(_s, Some(ref _rd)) => { + SCLogDebug!("OPENv4: status {} opendata {:?}", _s, _rd); + insert_filename_with_getfh = true; + } + Nfs4ResponseContent::GetFH(_s, Some(ref rd)) => { + if insert_filename_with_getfh { + self.namemap + .insert(rd.value.to_vec(), xidmap.file_name.to_vec()); + } + } + Nfs4ResponseContent::PutRootFH(s) => { + if s == NFS4_OK && xidmap.file_name.is_empty() { + xidmap.file_name = b"<mount_root>".to_vec(); + SCLogDebug!("filename {:?}", xidmap.file_name); + } + } + _ => {} + } + } + + if main_opcode_status_set { + let resp_handle = Vec::new(); + self.mark_response_tx_done(r.hdr.xid, r.reply_state, main_opcode_status, &resp_handle); + } + } + + pub fn process_reply_record_v4( + &mut self, r: &RpcReplyPacket, xidmap: &mut NFSRequestXidMap, + ) { + if xidmap.procedure == NFSPROC4_COMPOUND { + let mut data = r.prog_data; + + if xidmap.gssapi_proc == 0 && xidmap.gssapi_service == 2 { + SCLogDebug!("GSS INTEGRITY as set by call: {:?}", xidmap); + match parse_rpc_gssapi_integrity(r.prog_data) { + Ok((_rem, rec)) => { + SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec); + data = rec.data; + } + Err(Err::Incomplete(_n)) => { + SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n); + self.set_event(NFSEvent::MalformedData); + return; + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: Parsing failed: {:?}", _e); + self.set_event(NFSEvent::MalformedData); + return; + } + } + } + match parse_nfs4_response_compound(data) { + Ok((_, rd)) => { + SCLogDebug!("COMPOUNDv4: {:?}", rd); + self.compound_response(r, &rd, xidmap); + } + Err(Err::Incomplete(_)) => { + self.set_event(NFSEvent::MalformedData); + } + Err(Err::Error(_e)) | Err(Err::Failure(_e)) => { + SCLogDebug!("Parsing failed: {:?}", _e); + self.set_event(NFSEvent::MalformedData); + } + }; + } + } +} diff --git a/rust/src/nfs/nfs4_records.rs b/rust/src/nfs/nfs4_records.rs new file mode 100644 index 0000000..9d61da3 --- /dev/null +++ b/rust/src/nfs/nfs4_records.rs @@ -0,0 +1,2074 @@ +/* Copyright (C) 2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for NFSv4 records +use nom7::bytes::streaming::{tag, take}; +use nom7::combinator::{complete, cond, map, peek, verify, rest}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::{count, many_till}; +use nom7::number::streaming::{be_u32, be_u64}; +use nom7::{Err, IResult}; + +use crate::nfs::types::*; + +/*https://datatracker.ietf.org/doc/html/rfc7530 - section 16.16 File Delegation Types */ +const OPEN_DELEGATE_NONE: u32 = 0; +const OPEN_DELEGATE_READ: u32 = 1; +const OPEN_DELEGATE_WRITE: u32 = 2; + +const RPCSEC_GSS: u32 = 6; + +// Maximum number of operations per compound +// Linux defines NFSD_MAX_OPS_PER_COMPOUND to 16 (tested in Linux 5.15.1). +const NFSD_MAX_OPS_PER_COMPOUND: usize = 64; + +#[derive(Debug,PartialEq, Eq)] +pub enum Nfs4RequestContent<'a> { + PutFH(Nfs4Handle<'a>), + GetFH, + SaveFH, + PutRootFH, + ReadDir, + Commit, + Open(Nfs4RequestOpen<'a>), + Lookup(Nfs4RequestLookup<'a>), + Read(Nfs4RequestRead<'a>), + Write(Nfs4RequestWrite<'a>), + Close(Nfs4StateId<'a>), + Rename(Nfs4RequestRename<'a>), + Create(Nfs4RequestCreate<'a>), + OpenConfirm(Nfs4RequestOpenConfirm<'a>), + Access(u32), + GetAttr(Nfs4Attr), + SetAttr(Nfs4RequestSetAttr<'a>), + Renew(u64), + Remove(&'a[u8]), + DelegReturn(Nfs4StateId<'a>), + SetClientId(Nfs4RequestSetClientId<'a>), + SetClientIdConfirm, + ExchangeId(Nfs4RequestExchangeId<'a>), + Sequence(Nfs4RequestSequence<'a>), + CreateSession(Nfs4RequestCreateSession<'a>), + ReclaimComplete(u32), + SecInfoNoName(u32), + LayoutGet(Nfs4RequestLayoutGet<'a>), + GetDevInfo(Nfs4RequestGetDevInfo<'a>), + LayoutReturn(Nfs4RequestLayoutReturn<'a>), + DestroySession(&'a[u8]), + DestroyClientID(&'a[u8]), +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4Attr { + attr_mask: u64, +} + +fn nfs4_parse_attr_fields(i: &[u8]) -> IResult<&[u8], u32> { + let (i, len) = be_u32(i)?; + let (i, _) = take(len as usize)(i)?; + Ok((i, len)) +} + +fn nfs4_parse_attrs(i: &[u8]) -> IResult<&[u8], Nfs4Attr> { + let (i, attr_cnt) = be_u32(i)?; + let (i, attr_mask1) = be_u32(i)?; + let (i, attr_mask2) = cond(attr_cnt >= 2, be_u32)(i)?; + let (i, _) = cond(attr_cnt == 3, be_u32)(i)?; + let (i, _) = nfs4_parse_attr_fields(i)?; + let attr = Nfs4Attr { + attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64, + }; + Ok((i, attr)) +} + +fn nfs4_parse_attrbits(i: &[u8]) -> IResult<&[u8], Nfs4Attr> { + let (i, attr_cnt) = be_u32(i)?; + let (i, attr_mask1) = be_u32(i)?; + let (i, attr_mask2) = cond(attr_cnt >= 2, be_u32)(i)?; + let (i, _) = cond(attr_cnt == 3, be_u32)(i)?; + let attr = Nfs4Attr { + attr_mask: ((attr_mask1 as u64) << 32) | attr_mask2.unwrap_or(0) as u64, + }; + Ok((i, attr)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4StateId<'a> { + pub seqid: u32, + pub data: &'a[u8], +} + +fn nfs4_parse_stateid(i: &[u8]) -> IResult<&[u8], Nfs4StateId> { + let (i, seqid) = be_u32(i)?; + let (i, data) = take(12_usize)(i)?; + let state = Nfs4StateId { seqid, data }; + Ok((i, state)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4Handle<'a> { + pub len: u32, + pub value: &'a[u8], +} + +fn nfs4_parse_handle(i: &[u8]) -> IResult<&[u8], Nfs4Handle> { + let (i, len) = be_u32(i)?; + let (i, value) = take(len as usize)(i)?; + let handle = Nfs4Handle { len, value }; + Ok((i, handle)) +} + +fn nfs4_parse_nfsstring(i: &[u8]) -> IResult<&[u8], &[u8]> { + let (i, len) = be_u32(i)?; + let (i, data) = take(len as usize)(i)?; + let (i, _fill_bytes) = cond(len % 4 != 0, take(4 - (len % 4)))(i)?; + Ok((i, data)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4RequestLayoutReturn<'a> { + pub layout_type: u32, + pub return_type: u32, + pub length: u64, + pub stateid: Nfs4StateId<'a>, + pub lrf_data: &'a[u8], +} + +fn nfs4_req_layoutreturn(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _reclaim) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, layout_type) = be_u32(i)?; + let (i, _iq_mode) = be_u32(i)?; + let (i, return_type) = be_u32(i)?; + let (i, _offset) = be_u64(i)?; + let (i, length) = be_u64(i)?; + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, lrf_data) = nfs4_parse_nfsstring(i)?; + let req = Nfs4RequestContent::LayoutReturn(Nfs4RequestLayoutReturn { + layout_type, + return_type, + length, + stateid, + lrf_data, + }); + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4RequestGetDevInfo<'a> { + pub device_id: &'a[u8], + pub layout_type: u32, + pub maxcount: u32, + pub notify_mask: u32, +} + +fn nfs4_req_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, device_id) = take(16_usize)(i)?; + let (i, layout_type) = be_u32(i)?; + let (i, maxcount) = be_u32(i)?; + let (i, _) = be_u32(i)?; + let (i, notify_mask) = be_u32(i)?; + let req = Nfs4RequestContent::GetDevInfo(Nfs4RequestGetDevInfo { + device_id, + layout_type, + maxcount, + notify_mask, + }); + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4RequestCreateSession<'a> { + pub client_id: &'a[u8], + pub seqid: u32, + pub machine_name: &'a[u8], +} + +fn nfs4_req_create_session(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, client_id) = take(8_usize)(i)?; + let (i, seqid) = be_u32(i)?; + let (i, _flags) = be_u32(i)?; + let (i, _fore_chan_attrs) = take(28_usize)(i)?; + let (i, _back_chan_attrs) = take(28_usize)(i)?; + let (i, _cb_program) = be_u32(i)?; + let (i, _) = be_u32(i)?; + let (i, _flavor) = be_u32(i)?; + let (i, _stamp) = be_u32(i)?; + let (i, machine_name) = nfs4_parse_nfsstring(i)?; + let (i, _) = rest(i)?; + + let req = Nfs4RequestContent::CreateSession(Nfs4RequestCreateSession { + client_id, + seqid, + machine_name, + }); + Ok((i, req)) +} + +fn nfs4_req_putfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(nfs4_parse_handle, Nfs4RequestContent::PutFH)(i) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestSetClientId<'a> { + pub client_id: &'a[u8], + pub r_netid: &'a[u8], + pub r_addr: &'a[u8], +} + +fn nfs4_req_setclientid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _client_verifier) = take(8_usize)(i)?; + let (i, client_id) = nfs4_parse_nfsstring(i)?; + let (i, _cb_program) = be_u32(i)?; + let (i, r_netid) = nfs4_parse_nfsstring(i)?; + let (i, r_addr) = nfs4_parse_nfsstring(i)?; + let (i, _cb_id) = be_u32(i)?; + let req = Nfs4RequestContent::SetClientId(Nfs4RequestSetClientId { + client_id, + r_netid, + r_addr + }); + Ok((i, req)) +} + +fn nfs4_req_setclientid_confirm(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _client_id) = take(8_usize)(i)?; + let (i, _verifier) = take(8_usize)(i)?; + Ok((i, Nfs4RequestContent::SetClientIdConfirm)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestCreate<'a> { + pub ftype4: u32, + pub filename: &'a[u8], + pub link_content: &'a[u8], +} + +fn nfs4_req_create(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, ftype4) = be_u32(i)?; + let (i, link_content) = cond(ftype4 == 5, nfs4_parse_nfsstring)(i)?; + let (i, filename) = nfs4_parse_nfsstring(i)?; + let (i, _attrs) = nfs4_parse_attrs(i)?; + let req = Nfs4RequestContent::Create(Nfs4RequestCreate { + ftype4, + filename, + link_content: link_content.unwrap_or(&[]), + }); + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub enum Nfs4OpenRequestContent<'a> { + Exclusive4(&'a[u8]), + Unchecked4(Nfs4Attr), + Guarded4(Nfs4Attr), +} + +fn nfs4_req_open_unchecked4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> { + map(nfs4_parse_attrs, Nfs4OpenRequestContent::Unchecked4)(i) +} + +fn nfs4_req_open_guarded4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> { + map(nfs4_parse_attrs, Nfs4OpenRequestContent::Guarded4)(i) +} + +fn nfs4_req_open_exclusive4(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> { + map(take(8_usize), Nfs4OpenRequestContent::Exclusive4)(i) +} + +fn nfs4_req_open_type(i: &[u8]) -> IResult<&[u8], Nfs4OpenRequestContent> { + let (i, mode) = be_u32(i)?; + let (i, data) = match mode { + 0 => nfs4_req_open_unchecked4(i)?, + 1 => nfs4_req_open_guarded4(i)?, + 2 => nfs4_req_open_exclusive4(i)?, + _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); } + }; + Ok((i, data)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestOpen<'a> { + pub open_type: u32, + pub filename: &'a[u8], + pub open_data: Option<Nfs4OpenRequestContent<'a>>, +} + +fn nfs4_req_open(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _seq_id) = be_u32(i)?; + let (i, _share_access) = be_u32(i)?; + let (i, _share_deny) = be_u32(i)?; + let (i, _client_id) = be_u64(i)?; + let (i, owner_len) = be_u32(i)?; + let (i, _) = cond(owner_len > 0, take(owner_len as usize))(i)?; + let (i, open_type) = be_u32(i)?; + let (i, open_data) = cond(open_type == 1, nfs4_req_open_type)(i)?; + let (i, _claim_type) = be_u32(i)?; + let (i, filename) = nfs4_parse_nfsstring(i)?; + let req = Nfs4RequestContent::Open(Nfs4RequestOpen { + open_type, + filename, + open_data + }); + Ok((i, req)) +} + +fn nfs4_req_readdir(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _cookie) = be_u64(i)?; + let (i, _cookie_verf) = be_u64(i)?; + let (i, _dir_cnt) = be_u32(i)?; + let (i, _max_cnt) = be_u32(i)?; + let (i, _attr) = nfs4_parse_attrbits(i)?; + Ok((i, Nfs4RequestContent::ReadDir)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestRename<'a> { + pub oldname: &'a[u8], + pub newname: &'a[u8], +} + +fn nfs4_req_rename(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, oldname) = nfs4_parse_nfsstring(i)?; + let (i, newname) = nfs4_parse_nfsstring(i)?; + let req = Nfs4RequestContent::Rename(Nfs4RequestRename { + oldname, + newname + }); + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestLookup<'a> { + pub filename: &'a[u8], +} + +fn nfs4_req_destroy_session(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, ssn_id) = take(16_usize)(i)?; + Ok((i, Nfs4RequestContent::DestroySession(ssn_id))) +} + +fn nfs4_req_lookup(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(nfs4_parse_nfsstring, |filename| { + Nfs4RequestContent::Lookup(Nfs4RequestLookup { filename }) + })(i) +} + +fn nfs4_req_remove(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(nfs4_parse_nfsstring, Nfs4RequestContent::Remove)(i) +} + +fn nfs4_req_secinfo_no_name(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(be_u32, Nfs4RequestContent::SecInfoNoName) (i) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestSetAttr<'a> { + pub stateid: Nfs4StateId<'a>, +} + +fn nfs4_req_setattr(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _attrs) = nfs4_parse_attrs(i)?; + let req = Nfs4RequestContent::SetAttr(Nfs4RequestSetAttr { stateid }); + Ok((i, req)) +} + +fn nfs4_req_getattr(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(nfs4_parse_attrbits, Nfs4RequestContent::GetAttr)(i) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestWrite<'a> { + pub stateid: Nfs4StateId<'a>, + pub offset: u64, + pub stable: u32, + pub write_len: u32, + pub data: &'a[u8], +} + +fn nfs4_req_write(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, offset) = be_u64(i)?; + let (i, stable) = be_u32(i)?; + let (i, write_len) = be_u32(i)?; + let (i, data) = take(write_len as usize)(i)?; + let (i, _padding) = cond(write_len % 4 != 0, take(4 - (write_len % 4)))(i)?; + let req = Nfs4RequestContent::Write(Nfs4RequestWrite { + stateid, + offset, + stable, + write_len, + data, + }); + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestRead<'a> { + pub stateid: Nfs4StateId<'a>, + pub offset: u64, + pub count: u32, +} + +fn nfs4_req_read(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, offset) = be_u64(i)?; + let (i, count) = be_u32(i)?; + let req = Nfs4RequestContent::Read(Nfs4RequestRead { + stateid, + offset, + count, + }); + Ok((i, req)) +} + +fn nfs4_req_close(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _seq_id) = be_u32(i)?; + let (i, stateid) = nfs4_parse_stateid(i)?; + Ok((i, Nfs4RequestContent::Close(stateid))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestOpenConfirm<'a> { + pub stateid: Nfs4StateId<'a>, +} + +fn nfs4_req_open_confirm(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _seq_id) = be_u32(i)?; + let (i, stateid) = nfs4_parse_stateid(i)?; + let req = Nfs4RequestContent::OpenConfirm(Nfs4RequestOpenConfirm { + stateid + }); + Ok((i, req)) +} + +fn nfs4_req_delegreturn(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(nfs4_parse_stateid, Nfs4RequestContent::DelegReturn)(i) +} + +fn nfs4_req_renew(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(be_u64, Nfs4RequestContent::Renew)(i) +} + +fn nfs4_req_getfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + Ok((i, Nfs4RequestContent::GetFH)) +} + +fn nfs4_req_savefh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + Ok((i, Nfs4RequestContent::SaveFH)) +} + +fn nfs4_req_putrootfh(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + Ok((i, Nfs4RequestContent::PutRootFH)) +} + +fn nfs4_req_access(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(be_u32, Nfs4RequestContent::Access)(i) +} + +fn nfs4_req_commit(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _offset) = be_u64(i)?; + let (i, _count) = be_u32(i)?; + Ok((i, Nfs4RequestContent::Commit)) +} + +fn nfs4_req_reclaim_complete(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + map(verify(be_u32, |&v| v <= 1), Nfs4RequestContent::ReclaimComplete) (i) +} + +fn nfs4_req_destroy_clientid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, client_id) = take(8_usize)(i)?; + Ok((i, Nfs4RequestContent::DestroyClientID(client_id))) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4RequestLayoutGet<'a> { + pub layout_type: u32, + pub length: u64, + pub min_length: u64, + pub stateid: Nfs4StateId<'a>, +} + +fn nfs4_req_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _layout_available) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, layout_type) = be_u32(i)?; + let (i, _iq_mode) = be_u32(i)?; + let (i, _offset) = be_u64(i)?; + let (i, length) = be_u64(i)?; + let (i, min_length) = be_u64(i)?; + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _maxcount) = be_u32(i)?; + let req = Nfs4RequestContent::LayoutGet(Nfs4RequestLayoutGet { + layout_type, + length, + min_length, + stateid, + }); + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestExchangeId<'a> { + pub client_string: &'a[u8], + pub nii_domain: &'a[u8], + pub nii_name: &'a[u8], +} + +fn nfs4_req_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, _verifier) = take(8_usize)(i)?; + let (i, eia_clientstring) = nfs4_parse_nfsstring(i)?; + let (i, _eia_clientflags) = be_u32(i)?; + let (i, _eia_state_protect) = be_u32(i)?; + let (i, _eia_client_impl_id) = be_u32(i)?; + let (i, nii_domain) = nfs4_parse_nfsstring(i)?; + let (i, nii_name) = nfs4_parse_nfsstring(i)?; + let (i, _nii_data_sec) = be_u64(i)?; + let (i, _nii_data_nsec) = be_u32(i)?; + let req = Nfs4RequestContent::ExchangeId(Nfs4RequestExchangeId { + client_string: eia_clientstring, + nii_domain, + nii_name + }); + Ok((i, req)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestSequence<'a> { + pub ssn_id: &'a[u8], +} + +fn nfs4_req_sequence(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, ssn_id) = take(16_usize)(i)?; + let (i, _seq_id) = be_u32(i)?; + let (i, _slot_id) = be_u32(i)?; + let (i, _high_slot_id) = be_u32(i)?; + let (i, _cache_this) = be_u32(i)?; + let req = Nfs4RequestContent::Sequence(Nfs4RequestSequence { + ssn_id + }); + Ok((i, req)) +} + +fn parse_request_compound_command(i: &[u8]) -> IResult<&[u8], Nfs4RequestContent> { + let (i, cmd) = be_u32(i)?; + let (i, cmd_data) = match cmd { + NFSPROC4_PUTFH => nfs4_req_putfh(i)?, + NFSPROC4_READ => nfs4_req_read(i)?, + NFSPROC4_WRITE => nfs4_req_write(i)?, + NFSPROC4_GETFH => nfs4_req_getfh(i)?, + NFSPROC4_SAVEFH => nfs4_req_savefh(i)?, + NFSPROC4_OPEN => nfs4_req_open(i)?, + NFSPROC4_CLOSE => nfs4_req_close(i)?, + NFSPROC4_LOOKUP => nfs4_req_lookup(i)?, + NFSPROC4_ACCESS => nfs4_req_access(i)?, + NFSPROC4_COMMIT => nfs4_req_commit(i)?, + NFSPROC4_GETATTR => nfs4_req_getattr(i)?, + NFSPROC4_READDIR => nfs4_req_readdir(i)?, + NFSPROC4_RENEW => nfs4_req_renew(i)?, + NFSPROC4_OPEN_CONFIRM => nfs4_req_open_confirm(i)?, + NFSPROC4_REMOVE => nfs4_req_remove(i)?, + NFSPROC4_RENAME => nfs4_req_rename(i)?, + NFSPROC4_CREATE => nfs4_req_create(i)?, + NFSPROC4_DELEGRETURN => nfs4_req_delegreturn(i)?, + NFSPROC4_SETATTR => nfs4_req_setattr(i)?, + NFSPROC4_PUTROOTFH => nfs4_req_putrootfh(i)?, + NFSPROC4_SETCLIENTID => nfs4_req_setclientid(i)?, + NFSPROC4_SETCLIENTID_CONFIRM => nfs4_req_setclientid_confirm(i)?, + NFSPROC4_SEQUENCE => nfs4_req_sequence(i)?, + NFSPROC4_EXCHANGE_ID => nfs4_req_exchangeid(i)?, + NFSPROC4_CREATE_SESSION => nfs4_req_create_session(i)?, + NFSPROC4_RECLAIM_COMPLETE => nfs4_req_reclaim_complete(i)?, + NFSPROC4_SECINFO_NO_NAME => nfs4_req_secinfo_no_name(i)?, + NFSPROC4_LAYOUTGET => nfs4_req_layoutget(i)?, + NFSPROC4_GETDEVINFO => nfs4_req_getdevinfo(i)?, + NFSPROC4_LAYOUTRETURN => nfs4_req_layoutreturn(i)?, + NFSPROC4_DESTROY_SESSION => nfs4_req_destroy_session(i)?, + NFSPROC4_DESTROY_CLIENTID => nfs4_req_destroy_clientid(i)?, + _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); } + }; + Ok((i, cmd_data)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4RequestCompoundRecord<'a> { + pub commands: Vec<Nfs4RequestContent<'a>>, +} + +pub fn parse_nfs4_request_compound(i: &[u8]) -> IResult<&[u8], Nfs4RequestCompoundRecord> { + let (i, tag_len) = be_u32(i)?; + let (i, _tag) = cond(tag_len > 0, take(tag_len as usize))(i)?; + let (i, _min_ver) = be_u32(i)?; + let (i, ops_cnt) = be_u32(i)?; + if ops_cnt as usize > NFSD_MAX_OPS_PER_COMPOUND { + return Err(Err::Error(make_error(i, ErrorKind::Count))); + } + let (i, commands) = count(parse_request_compound_command, ops_cnt as usize)(i)?; + Ok((i, Nfs4RequestCompoundRecord { commands })) +} + +#[derive(Debug,PartialEq, Eq)] +pub enum Nfs4ResponseContent<'a> { + PutFH(u32), + PutRootFH(u32), + GetFH(u32, Option<Nfs4Handle<'a>>), + Lookup(u32), + SaveFH(u32), + Rename(u32), + Write(u32, Option<Nfs4ResponseWrite>), + Read(u32, Option<Nfs4ResponseRead<'a>>), + Renew(u32), + Open(u32, Option<Nfs4ResponseOpen<'a>>), + OpenConfirm(u32, Option<Nfs4StateId<'a>>), + Close(u32, Option<Nfs4StateId<'a>>), + GetAttr(u32, Option<Nfs4Attr>), + SetAttr(u32), + Access(u32, Option<Nfs4ResponseAccess>), + ReadDir(u32, Option<Nfs4ResponseReaddir<'a>>), + Remove(u32), + DelegReturn(u32), + SetClientId(u32), + SetClientIdConfirm(u32), + Create(u32), + Commit(u32), + ExchangeId(u32, Option<Nfs4ResponseExchangeId<'a>>), + Sequence(u32, Option<Nfs4ResponseSequence<'a>>), + CreateSession(u32, Option<Nfs4ResponseCreateSession<'a>>), + ReclaimComplete(u32), + SecInfoNoName(u32), + LayoutGet(u32, Option<Nfs4ResponseLayoutGet<'a>>), + GetDevInfo(u32, Option<Nfs4ResponseGetDevInfo<'a>>), + LayoutReturn(u32), + DestroySession(u32), + DestroyClientID(u32), +} + +// might need improvement with a stateid_present = yes case +fn nfs4_res_layoutreturn(i:&[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _stateid_present) = verify(be_u32, |&v| v <= 1)(i)?; + Ok((i, Nfs4ResponseContent::LayoutReturn(status))) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4ResponseCreateSession<'a> { + pub ssn_id: &'a[u8], + pub seq_id: u32, +} + +fn nfs4_parse_res_create_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseCreateSession> { + let (i, ssn_id) = take(16_usize)(i)?; + let (i, seq_id) = be_u32(i)?; + let (i, _flags) = be_u32(i)?; + let (i, _fore_chan_attrs) = take(28_usize)(i)?; + let (i, _back_chan_attrs) = take(28_usize)(i)?; + Ok((i, Nfs4ResponseCreateSession { + ssn_id, + seq_id + })) +} + +fn nfs4_res_create_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, create_ssn_data) = cond(status == 0, nfs4_parse_res_create_session)(i)?; + Ok((i, Nfs4ResponseContent::CreateSession( status, create_ssn_data ))) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4ResponseExchangeId<'a> { + pub client_id: &'a[u8], + pub eir_minorid: u64, + pub eir_majorid: &'a[u8], + pub nii_domain: &'a[u8], + pub nii_name: &'a[u8], +} + +fn nfs4_parse_res_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseExchangeId> { + let (i, client_id) = take(8_usize)(i)?; + let (i, _seqid) = be_u32(i)?; + let (i, _flags) = be_u32(i)?; + let (i, _eia_state_protect) = be_u32(i)?; + let (i, eir_minorid) = be_u64(i)?; + let (i, eir_majorid) = nfs4_parse_nfsstring(i)?; + let (i, _server_scope) = nfs4_parse_nfsstring(i)?; + let (i, _eir_impl_id) = be_u32(i)?; + let (i, nii_domain) = nfs4_parse_nfsstring(i)?; + let (i, nii_name) = nfs4_parse_nfsstring(i)?; + let (i, _nii_date_sec) = be_u64(i)?; + let (i, _nii_date_nsec) = be_u32(i)?; + Ok((i, Nfs4ResponseExchangeId { + client_id, + eir_minorid, + eir_majorid, + nii_domain, + nii_name, + })) +} + +fn nfs4_res_reclaim_complete(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::ReclaimComplete) (i) +} + +fn nfs4_res_exchangeid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, xchngid_data) = cond(status == 0, nfs4_parse_res_exchangeid)(i)?; + Ok((i, Nfs4ResponseContent::ExchangeId( status, xchngid_data))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseWrite { + pub count: u32, + pub committed: u32, +} + +fn nfs4_res_write_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseWrite> { + let (i, count) = be_u32(i)?; + let (i, committed) = be_u32(i)?; + let (i, _verifier) = be_u64(i)?; + Ok((i, Nfs4ResponseWrite { count, committed })) +} + +fn nfs4_res_write(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, wd) = cond(status == 0, nfs4_res_write_ok)(i)?; + Ok((i, Nfs4ResponseContent::Write(status, wd))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseRead<'a> { + pub eof: bool, + pub count: u32, + pub data: &'a[u8], +} + +fn nfs4_res_read_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseRead> { + let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, read_len) = be_u32(i)?; + let (i, read_data) = take(read_len as usize)(i)?; + let resp = Nfs4ResponseRead { + eof: eof==1, + count: read_len, + data: read_data, + }; + Ok((i, resp)) +} + +fn nfs4_res_read(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, rd) = cond(status == 0, nfs4_res_read_ok)(i)?; + Ok((i, Nfs4ResponseContent::Read(status, rd))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseOpen<'a> { + pub stateid: Nfs4StateId<'a>, + pub result_flags: u32, + pub delegate: Nfs4ResponseFileDelegation<'a>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Nfs4ResponseFileDelegation<'a> { + DelegateRead(Nfs4ResponseOpenDelegateRead<'a>), + DelegateWrite(Nfs4ResponseOpenDelegateWrite<'a>), + DelegateNone(u32), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4ResponseOpenDelegateWrite<'a> { + pub stateid: Nfs4StateId<'a>, + pub who: &'a[u8], +} + +fn nfs4_res_open_ok_delegate_write(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _recall) = be_u32(i)?; + let (i, _space_limit) = be_u32(i)?; + let (i, _filesize) = be_u32(i)?; + let (i, _access_type) = be_u32(i)?; + let (i, _ace_flags) = be_u32(i)?; + let (i, _ace_mask) = be_u32(i)?; + let (i, who) = nfs4_parse_nfsstring(i)?; + Ok((i, Nfs4ResponseFileDelegation::DelegateWrite(Nfs4ResponseOpenDelegateWrite { + stateid, + who, + }))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseOpenDelegateRead<'a> { + pub stateid: Nfs4StateId<'a>, +} + +fn nfs4_res_open_ok_delegate_read(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _recall) = be_u32(i)?; + let (i, _ace_type) = be_u32(i)?; + let (i, _ace_flags) = be_u32(i)?; + let (i, _ace_mask) = be_u32(i)?; + let (i, who_len) = be_u32(i)?; + let (i, _who) = take(who_len as usize)(i)?; + Ok((i, Nfs4ResponseFileDelegation::DelegateRead(Nfs4ResponseOpenDelegateRead { + stateid, + }))) +} + +fn nfs4_parse_file_delegation(i: &[u8]) -> IResult<&[u8], Nfs4ResponseFileDelegation> { + let (i, delegation_type) = be_u32(i)?; + let (i, file_delegation) = match delegation_type { + OPEN_DELEGATE_READ => nfs4_res_open_ok_delegate_read(i)?, + OPEN_DELEGATE_WRITE => nfs4_res_open_ok_delegate_write(i)?, + OPEN_DELEGATE_NONE => (i, Nfs4ResponseFileDelegation::DelegateNone(OPEN_DELEGATE_NONE)), + _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); } + }; + Ok((i, file_delegation)) +} + +fn nfs4_res_open_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseOpen> { + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _change_info) = take(20_usize)(i)?; + let (i, result_flags) = be_u32(i)?; + let (i, _attrs) = nfs4_parse_attrbits(i)?; + let (i, delegate) = nfs4_parse_file_delegation(i)?; + let resp = Nfs4ResponseOpen { + stateid, + result_flags, + delegate, + }; + Ok((i, resp)) +} + +fn nfs4_res_open(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, open_data) = cond(status == 0, nfs4_res_open_ok)(i)?; + Ok((i, Nfs4ResponseContent::Open(status, open_data))) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4ResponseGetDevInfo<'a> { + pub layout_type: u32, + pub r_netid: &'a[u8], + pub r_addr: &'a[u8], + pub notify_mask: u32, +} + +fn nfs4_parse_res_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4ResponseGetDevInfo> { + let (i, layout_type) = be_u32(i)?; + let (i, _) = be_u64(i)?; + let (i, _device_index) = be_u32(i)?; + let (i, _) = be_u64(i)?; + let (i, r_netid) = nfs4_parse_nfsstring(i)?; + let (i, r_addr) = nfs4_parse_nfsstring(i)?; + let (i, notify_mask) = be_u32(i)?; + Ok((i, Nfs4ResponseGetDevInfo { + layout_type, + r_netid, + r_addr, + notify_mask, + })) +} + +fn nfs4_res_getdevinfo(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, getdevinfo) = cond(status == 0, nfs4_parse_res_getdevinfo)(i)?; + Ok((i, Nfs4ResponseContent::GetDevInfo( status, getdevinfo ))) +} + +/*https://datatracker.ietf.org/doc/html/rfc5661#section-13.1*/ +// in case of multiple file handles, return handles in a vector +#[derive(Debug, PartialEq, Eq)] +pub struct Nfs4ResponseLayoutGet<'a> { + pub stateid: Nfs4StateId<'a>, + pub length: u64, + pub layout_type: u32, + pub device_id: &'a[u8], + pub file_handles: Vec<Nfs4Handle<'a>>, +} + +fn nfs4_parse_res_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4ResponseLayoutGet> { + let (i, _return_on_close) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, stateid) = nfs4_parse_stateid(i)?; + let (i, _layout_seg) = be_u32(i)?; + let (i, _offset) = be_u64(i)?; + let (i, length) = be_u64(i)?; + let (i, _lo_mode) = be_u32(i)?; + let (i, layout_type) = be_u32(i)?; + let (i, _) = be_u32(i)?; + let (i, device_id) = take(16_usize)(i)?; + let (i, _nfl_util) = be_u32(i)?; + let (i, _strip_index) = be_u32(i)?; + let (i, _offset) = be_u64(i)?; + let (i, fh_handles) = be_u32(i)?; + // check before `count` allocates a vector + // so as not to run out of memory + if fh_handles as usize > 4 * i.len() { + return Err(Err::Error(make_error(i, ErrorKind::Count))); + } + let (i, file_handles) = count(nfs4_parse_handle, fh_handles as usize)(i)?; + Ok((i, Nfs4ResponseLayoutGet { + stateid, + length, + layout_type, + device_id, + file_handles, + })) +} + +fn nfs4_res_layoutget(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, lyg_data) = cond(status == 0, nfs4_parse_res_layoutget)(i)?; + Ok((i, Nfs4ResponseContent::LayoutGet( status, lyg_data ))) +} + +// #[derive(Debug, PartialEq)] +// pub struct Nfs4FlavorRpcSecGss<'a> { +// pub oid: &'a[u8], +// pub qop: u32, +// pub service: u32, +// } + +fn nfs4_parse_rpcsec_gss(i: &[u8]) -> IResult<&[u8], u32> { + let (i, _oid) = nfs4_parse_nfsstring(i)?; + let (i, _qop) = be_u32(i)?; + let (i, _service) = be_u32(i)?; + Ok((i, RPCSEC_GSS)) +} + +fn nfs4_parse_flavors(i: &[u8]) -> IResult<&[u8], u32> { + let (i, flavor_type) = be_u32(i)?; + let (i, _flavor) = cond(flavor_type == RPCSEC_GSS, nfs4_parse_rpcsec_gss)(i)?; + Ok((i, flavor_type)) +} + +fn nfs4_res_secinfo_no_name(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, flavors_cnt) = be_u32(i)?; + // do not use nom's count as it allocates a Vector first + // which results in oom if flavors_cnt is really big, bigger than i.len() + let mut i2 = i; + for _n in 0..flavors_cnt { + let (i3, _flavor) = nfs4_parse_flavors(i2)?; + i2 = i3; + } + Ok((i, Nfs4ResponseContent::SecInfoNoName(status))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseReaddirEntry<'a> { + pub name: &'a[u8], +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseReaddir<'a> { + pub eof: bool, + pub listing: Vec<Option<Nfs4ResponseReaddirEntry<'a>>>, +} + +fn nfs4_res_readdir_entry_do(i: &[u8]) -> IResult<&[u8], Nfs4ResponseReaddirEntry> { + let (i, _cookie) = be_u64(i)?; + let (i, name) = nfs4_parse_nfsstring(i)?; + let (i, _attrs) = nfs4_parse_attrs(i)?; + Ok((i, Nfs4ResponseReaddirEntry { name })) +} + +fn nfs4_res_readdir_entry(i: &[u8]) -> IResult<&[u8], Option<Nfs4ResponseReaddirEntry>> { + let (i, value_follows) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, entry) = cond(value_follows == 1, nfs4_res_readdir_entry_do)(i)?; + Ok((i, entry)) +} + +fn nfs4_res_readdir_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseReaddir> { + let (i, _verifier) = be_u64(i)?; + // run parser until we find a 'value follows == 0' + let (i, listing) = many_till( + complete(nfs4_res_readdir_entry), + peek(tag(b"\x00\x00\x00\x00")), + )(i)?; + // value follows == 0 checked by line above + let (i, _value_follows) = tag(b"\x00\x00\x00\x00")(i)?; + let (i, eof) = verify(be_u32, |&v| v <= 1)(i)?; + Ok(( + i, + Nfs4ResponseReaddir { + eof: eof == 1, + listing: listing.0, + }, + )) +} + +fn nfs4_res_readdir(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, rd) = cond(status == 0, nfs4_res_readdir_ok)(i)?; + Ok((i, Nfs4ResponseContent::ReadDir(status, rd))) +} + +fn nfs4_res_create_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> { + let (i, _change_info) = take(20_usize)(i)?; + nfs4_parse_attrbits(i) +} + +fn nfs4_res_create(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _attrs) = cond(status == 0, nfs4_res_create_ok)(i)?; + Ok((i, Nfs4ResponseContent::Create(status))) +} + +fn nfs4_res_setattr_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> { + nfs4_parse_attrbits(i) +} + +fn nfs4_res_setattr(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _attrs) = cond(status == 0, nfs4_res_setattr_ok)(i)?; + Ok((i, Nfs4ResponseContent::SetAttr(status))) +} + +fn nfs4_res_getattr_ok(i: &[u8]) -> IResult<&[u8], Nfs4Attr> { + nfs4_parse_attrs(i) +} + +fn nfs4_res_getattr(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, attrs) = cond(status == 0, nfs4_res_getattr_ok)(i)?; + Ok((i, Nfs4ResponseContent::GetAttr(status, attrs))) +} + +fn nfs4_res_openconfirm(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, stateid) = cond(status == 0, nfs4_parse_stateid)(i)?; + Ok((i, Nfs4ResponseContent::OpenConfirm(status, stateid))) +} + +fn nfs4_res_close(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, stateid) = cond(status == 0, nfs4_parse_stateid)(i)?; + Ok((i, Nfs4ResponseContent::Close(status, stateid))) +} + +fn nfs4_res_remove(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _) = cond(status == 0, take(20_usize))(i)?; + Ok((i, Nfs4ResponseContent::Remove(status))) +} + +fn nfs4_res_rename(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::Rename)(i) +} + +fn nfs4_res_savefh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::SaveFH)(i) +} + +fn nfs4_res_lookup(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::Lookup)(i) +} + +fn nfs4_res_renew(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::Renew)(i) +} + +fn nfs4_res_getfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, fh) = cond(status == 0, nfs4_parse_handle)(i)?; + Ok((i, Nfs4ResponseContent::GetFH(status, fh))) +} + +fn nfs4_res_putfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::PutFH)(i) +} + +fn nfs4_res_putrootfh(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::PutRootFH)(i) +} + +fn nfs4_res_delegreturn(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::DelegReturn)(i) +} + +fn nfs4_res_setclientid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _client_id) = be_u64(i)?; + let (i, _verifier) = be_u32(i)?; + Ok((i, Nfs4ResponseContent::SetClientId(status))) +} + +fn nfs4_res_setclientid_confirm(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::SetClientIdConfirm)(i) +} + +fn nfs4_res_commit(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, _verifier) = cond(status == 0, take(8_usize))(i)?; + Ok((i, Nfs4ResponseContent::Commit(status))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseAccess { + pub supported_types: u32, + pub access_rights: u32, +} + +fn nfs4_res_access_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseAccess> { + let (i, supported_types) = be_u32(i)?; + let (i, access_rights) = be_u32(i)?; + let resp = Nfs4ResponseAccess { + supported_types, + access_rights + }; + Ok((i, resp)) +} + +fn nfs4_res_access(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, ad) = cond(status == 0, nfs4_res_access_ok)(i)?; + Ok((i, Nfs4ResponseContent::Access(status, ad))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseSequence<'a> { + pub ssn_id: &'a[u8], +} + +fn nfs4_res_sequence_ok(i: &[u8]) -> IResult<&[u8], Nfs4ResponseSequence> { + let (i, ssn_id) = take(16_usize)(i)?; + let (i, _seqid) = be_u32(i)?; + let (i, _slots) = take(12_usize)(i)?; + let (i, _flags) = be_u32(i)?; + Ok((i, Nfs4ResponseSequence { ssn_id })) +} + +fn nfs4_res_sequence(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, status) = be_u32(i)?; + let (i, seq) = cond(status == 0, nfs4_res_sequence_ok)(i)?; + Ok((i, Nfs4ResponseContent::Sequence(status, seq))) +} + +fn nfs4_res_destroy_session(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::DestroySession) (i) +} + +fn nfs4_res_destroy_clientid(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + map(be_u32, Nfs4ResponseContent::DestroyClientID) (i) +} + +fn nfs4_res_compound_command(i: &[u8]) -> IResult<&[u8], Nfs4ResponseContent> { + let (i, cmd) = be_u32(i)?; + let (i, cmd_data) = match cmd { + NFSPROC4_READ => nfs4_res_read(i)?, + NFSPROC4_WRITE => nfs4_res_write(i)?, + NFSPROC4_ACCESS => nfs4_res_access(i)?, + NFSPROC4_COMMIT => nfs4_res_commit(i)?, + NFSPROC4_GETFH => nfs4_res_getfh(i)?, + NFSPROC4_PUTFH => nfs4_res_putfh(i)?, + NFSPROC4_SAVEFH => nfs4_res_savefh(i)?, + NFSPROC4_RENAME => nfs4_res_rename(i)?, + NFSPROC4_READDIR => nfs4_res_readdir(i)?, + NFSPROC4_GETATTR => nfs4_res_getattr(i)?, + NFSPROC4_SETATTR => nfs4_res_setattr(i)?, + NFSPROC4_LOOKUP => nfs4_res_lookup(i)?, + NFSPROC4_OPEN => nfs4_res_open(i)?, + NFSPROC4_OPEN_CONFIRM => nfs4_res_openconfirm(i)?, + NFSPROC4_CLOSE => nfs4_res_close(i)?, + NFSPROC4_REMOVE => nfs4_res_remove(i)?, + NFSPROC4_CREATE => nfs4_res_create(i)?, + NFSPROC4_DELEGRETURN => nfs4_res_delegreturn(i)?, + NFSPROC4_SETCLIENTID => nfs4_res_setclientid(i)?, + NFSPROC4_SETCLIENTID_CONFIRM => nfs4_res_setclientid_confirm(i)?, + NFSPROC4_PUTROOTFH => nfs4_res_putrootfh(i)?, + NFSPROC4_EXCHANGE_ID => nfs4_res_exchangeid(i)?, + NFSPROC4_SEQUENCE => nfs4_res_sequence(i)?, + NFSPROC4_RENEW => nfs4_res_renew(i)?, + NFSPROC4_CREATE_SESSION => nfs4_res_create_session(i)?, + NFSPROC4_RECLAIM_COMPLETE => nfs4_res_reclaim_complete(i)?, + NFSPROC4_SECINFO_NO_NAME => nfs4_res_secinfo_no_name(i)?, + NFSPROC4_LAYOUTGET => nfs4_res_layoutget(i)?, + NFSPROC4_GETDEVINFO => nfs4_res_getdevinfo(i)?, + NFSPROC4_LAYOUTRETURN => nfs4_res_layoutreturn(i)?, + NFSPROC4_DESTROY_SESSION => nfs4_res_destroy_session(i)?, + NFSPROC4_DESTROY_CLIENTID => nfs4_res_destroy_clientid(i)?, + _ => { return Err(Err::Error(make_error(i, ErrorKind::Switch))); } + }; + Ok((i, cmd_data)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Nfs4ResponseCompoundRecord<'a> { + pub status: u32, + pub commands: Vec<Nfs4ResponseContent<'a>>, +} + +pub fn parse_nfs4_response_compound(i: &[u8]) -> IResult<&[u8], Nfs4ResponseCompoundRecord> { + let (i, status) = be_u32(i)?; + let (i, tag_len) = be_u32(i)?; + let (i, _tag) = cond(tag_len > 0, take(tag_len as usize))(i)?; + let (i, ops_cnt) = be_u32(i)?; + if ops_cnt as usize > NFSD_MAX_OPS_PER_COMPOUND { + return Err(Err::Error(make_error(i, ErrorKind::Count))); + } + let (i, commands) = count(nfs4_res_compound_command, ops_cnt as usize)(i)?; + Ok((i, Nfs4ResponseCompoundRecord { status, commands })) +} + +#[cfg(test)] +mod tests { + use crate::nfs::nfs4_records::*; + + #[test] + fn test_nfs4_request_compound() { + // Operations: SEQUENCE, PUTFH, CLOSE + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*Tag*/ + 0x00, 0x00, 0x00, 0x01, /*min_ver*/ + 0x00, 0x00, 0x00, 0x03, /*ops_cnt*/ + // SEQUENCE + 0x00, 0x00, 0x00, 0x35, /*op_code*/ + 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, + 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + // PUTFH + 0x00, 0x00, 0x00, 0x16, /*op_code*/ + 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x72, 0x00, 0x00, 0x23, 0xa6, 0xc0, 0x12, + 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + // CLOSE + 0x00, 0x00, 0x00, 0x04, /*op_code*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x88, 0xd9, + 0x04, 0x00, 0x00, 0x00, + ]; + + let sequence_buf: &[u8] = &buf[16..48]; + let putfh_buf: &[u8] = &buf[52..88]; + let close_buf: &[u8] = &buf[92..]; + + let (_, req_sequence) = nfs4_req_sequence(sequence_buf).unwrap(); + let (_, req_putfh) = nfs4_req_putfh(putfh_buf).unwrap(); + let (_, req_close) = nfs4_req_close(close_buf).unwrap(); + + let (_, compound_ops) = parse_nfs4_request_compound(buf).unwrap(); + assert_eq!(compound_ops.commands[0], req_sequence); + assert_eq!(compound_ops.commands[1], req_putfh); + assert_eq!(compound_ops.commands[2], req_close); + } + + #[test] + fn test_nfs4_request_setclientid() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x23, /*opcode*/ + 0x59, 0x1b, 0x09, 0x04, 0x28, 0x9c, 0x5d, 0x10, /*_verifier*/ + 0x00, 0x00, 0x00, 0x2d, 0x4c, 0x69, 0x6e, 0x75, /*client_id*/ + 0x78, 0x20, 0x4e, 0x46, 0x53, 0x76, 0x34, 0x2e, + 0x30, 0x20, 0x31, 0x30, 0x2e, 0x31, 0x39, 0x33, + 0x2e, 0x36, 0x37, 0x2e, 0x32, 0x32, 0x35, 0x2f, + 0x31, 0x30, 0x2e, 0x31, 0x39, 0x33, 0x2e, 0x36, + 0x37, 0x2e, 0x32, 0x31, 0x39, 0x20, 0x74, 0x63, + 0x70, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, /*_cb_program*/ + 0x00, 0x00, 0x00, 0x03, 0x74, 0x63, 0x70, 0x00, /*r_netid*/ + 0x00, 0x00, 0x00, 0x14, 0x31, 0x30, 0x2e, 0x31, /*r_addr*/ + 0x39, 0x33, 0x2e, 0x36, 0x37, 0x2e, 0x32, 0x32, + 0x35, 0x2e, 0x31, 0x34, 0x30, 0x2e, 0x31, 0x38, + 0x00, 0x00, 0x00, 0x01, /*_cb_id*/ + ]; + + let (_, req_client_id) = nfs4_parse_nfsstring(&buf[12..64]).unwrap(); + let (_, req_r_netid) = nfs4_parse_nfsstring(&buf[68 ..76]).unwrap(); + let (_, req_r_adrr) = nfs4_parse_nfsstring(&buf[76..100]).unwrap(); + + let (_, resquest) = nfs4_req_setclientid(&buf[4..]).unwrap(); + match resquest { + Nfs4RequestContent::SetClientId( req_setclientid ) => { + assert_eq!(req_setclientid.client_id, req_client_id); + assert_eq!(req_setclientid.r_netid, req_r_netid); + assert_eq!(req_setclientid.r_addr, req_r_adrr); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_request_open() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x12, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*_seq_id*/ + 0x00, 0x00, 0x00, 0x02, /*_share_access*/ + 0x00, 0x00, 0x00, 0x00, /*_share_deny*/ + 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2, /*_client_id*/ + // OWNER + 0x00, 0x00, 0x00, 0x18, /*owner_len*/ + 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x69, 0x64, 0x3a, + 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x48, 0x0c, 0xae, 0x9b, 0x05, 0x08, + // OPEN + 0x00, 0x00, 0x00, 0x01, /*open_type: OPEN4_CREATE*/ + 0x00, 0x00, 0x00, 0x00, /*create_mode: UNCHECKED4*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10, /*attr_mask*/ + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0xb4, + // CLAIM_TYPE + 0x00, 0x00, 0x00, 0x00, /*_claim_type: CLAIM_NULL*/ + 0x00, 0x00, 0x00, 0x04, 0x66, 0x69, 0x6c, 0x65, /*filename*/ + ]; + + let (_, attr_buf) = nfs4_parse_attrbits(&buf[60..88]).unwrap(); + let (_, filename_buf) = nfs4_parse_nfsstring(&buf[92..]).unwrap(); + + let (_, request) = nfs4_req_open(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::Open(req_open) => { + assert_eq!(req_open.open_type, 1); + assert_eq!(req_open.open_data, Some(Nfs4OpenRequestContent::Unchecked4(attr_buf))); + assert_eq!(req_open.filename, filename_buf); + } + _ => { panic!("Failure, {:?}", request); } + } + } + + #[test] + fn test_nfs4_request_write() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x26, /*op_code*/ + 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x14, 0xe0, /*stateid*/ + 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*offset*/ + 0x00, 0x00, 0x00, 0x02, /*stable*/ + 0x00, 0x00, 0x00, 0x05, /*write_len*/ + 0x74, 0x65, 0x73, 0x74, 0x0a, /*data*/ + 0x00, 0x00, 0x00, /*_padding*/ + ]; + + let (_, stateid_buf) = nfs4_parse_stateid(&buf[4..20]).unwrap(); + + let (_, request) = nfs4_req_write(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::Write(req_write) => { + assert_eq!(req_write.stateid, stateid_buf); + assert_eq!(req_write.offset, 0); + assert_eq!(req_write.stable, 2); + assert_eq!(req_write.write_len, 5); + assert_eq!(req_write.data, "test\n".as_bytes()); + } + _ => { panic!("Failure, {:?}", request); } + } + } + + #[test] + fn test_nfs4_request_exchangeid() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2a, /*opcode*/ + // eia_clientowner + 0x5c, 0x8a, 0x9b, 0xfe, 0x0c, 0x09, 0x5e, 0x92, /*_verifier*/ + 0x00, 0x00, 0x00, 0x17, 0x4c, 0x69, 0x6e, 0x75, /*eia_clientstring*/ + 0x78, 0x20, 0x4e, 0x46, 0x53, 0x76, 0x34, 0x2e, + 0x31, 0x20, 0x6e, 0x65, 0x74, 0x61, 0x70, 0x70, + 0x2d, 0x32, 0x36, 0x00, + 0x00, 0x00, 0x01, 0x01, /*_eia_clientflags*/ + 0x00, 0x00, 0x00, 0x00, /*_eia_state_protect*/ + // _eia_client_impl_id + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x72, 0x6e, /*nii_domain*/ + 0x65, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x45, 0x4c, 0x69, 0x6e, 0x75, /*nii_name*/ + 0x78, 0x20, 0x33, 0x2e, 0x31, 0x30, 0x2e, 0x30, + 0x2d, 0x39, 0x35, 0x37, 0x2e, 0x65, 0x6c, 0x37, + 0x2e, 0x78, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x20, + 0x23, 0x31, 0x20, 0x53, 0x4d, 0x50, 0x20, 0x54, + 0x68, 0x75, 0x20, 0x4f, 0x63, 0x74, 0x20, 0x34, + 0x20, 0x32, 0x30, 0x3a, 0x34, 0x38, 0x3a, 0x35, + 0x31, 0x20, 0x55, 0x54, 0x43, 0x20, 0x32, 0x30, + 0x31, 0x38, 0x20, 0x78, 0x38, 0x36, 0x5f, 0x36, + 0x34, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_nii_data_sec*/ + 0x00, 0x00, 0x00, 0x00, /*_nii_data_nsec*/ + ]; + + /*( .Linux NFSv4.1 netapp-26 )*/ + let (_, client_string_buf) = nfs4_parse_nfsstring(&buf[12..40]).unwrap(); + /*(kernel.org\0\0\0\n)*/ + let (_, nii_domain_buf) = nfs4_parse_nfsstring(&buf[52..68]).unwrap(); + /* ( ELinux 3.10.0-957.el7.x86_64 #1 SMP Thu Oct 4 20:48:51 UTC 2018 x86_64 ) */ + let (_, nii_name_buf) = nfs4_parse_nfsstring(&buf[68..144]).unwrap(); + + let (_, request) = nfs4_req_exchangeid(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::ExchangeId(req_exchangeid) => { + assert_eq!(req_exchangeid.client_string, client_string_buf); + assert_eq!(req_exchangeid.nii_domain, nii_domain_buf); + assert_eq!(req_exchangeid.nii_name, nii_name_buf); + } + _ => { panic!("Failure, {:?}", request); } + } + } + + #[test] + fn test_nfs4_request_close() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x04, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*_seq_id*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x82, 0x14, 0xe0, /*stateid*/ + 0x5b, 0x00, 0x88, 0xd9, 0x04, 0x00, 0x00, 0x00, + ]; + + let (_, stateid_buf) = nfs4_parse_stateid(&buf[8..]).unwrap(); + + let (_, request) = nfs4_req_close(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::Close(req_stateid) => { + assert_eq!(req_stateid, stateid_buf); + } + _ => { panic!("Failure, {:?}", request); } + } + } + + #[test] + fn test_nfs4_request_sequence() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x35, /*opcode*/ + 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, /*ssn_id*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, + 0x00, 0x00, 0x00, 0x18, /*_seq_id*/ + 0x00, 0x00, 0x00, 0x00, /*_slot_id*/ + 0x00, 0x00, 0x00, 0x00, /*_high_slot_id*/ + 0x00, 0x00, 0x00, 0x01, /*_catch_this*/ + ]; + + let (_, req_sequence) = nfs4_req_sequence(&buf[4..]).unwrap(); + match req_sequence { + Nfs4RequestContent::Sequence(seq_buf) => { + assert_eq!(seq_buf.ssn_id, &buf[4..20]); + } + _ => { panic!("Failure, {:?}", req_sequence); } + } + } + + #[test] + fn test_nfs4_request_lookup() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x0f, /*opcode*/ + 0x00, 0x00, 0x00, 0x04, 0x76, 0x6f, 0x6c, 0x31, /*filename: (vol1)*/ + ]; + + let (_, filename_buf) = nfs4_parse_nfsstring(&buf[4..]).unwrap(); + + let (_, request) = nfs4_req_lookup(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::Lookup(req_lookup) => { + assert_eq!(req_lookup.filename, filename_buf); + } + _ => { panic!("Failure, {:?}", request); } + } + } + + #[test] + fn test_nfs4_request_putfh() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x16, /*opcode*/ + 0x00, 0x00, 0x00, 0x20, /*handle_len*/ + 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*handle*/ + 0x00, 0x00, 0x00, 0x00, 0x84, 0x72, 0x00, 0x00, + 0x23, 0xa6, 0xc0, 0x12, 0x00, 0xf2, 0xfa, 0x80, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, handle_buf) = nfs4_parse_handle(&buf[4..]).unwrap(); + + let (_, result) = nfs4_req_putfh(&buf[4..]).unwrap(); + match result { + Nfs4RequestContent::PutFH(putfh_handle) => { + assert_eq!(putfh_handle.value, handle_buf.value); + assert_eq!(putfh_handle.len, handle_buf.len); + } + _ => { panic!("Failure, {:?}", result); } + } + } + + #[test] + fn test_nfs4_request_create_session() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2b, /*opcode*/ + 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2, // create_session + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x04, 0x14, + 0x00, 0x10, 0x03, 0x88, 0x00, 0x00, 0x0d, 0x64, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x01, 0x0c, 0x09, 0x5e, 0x92, + 0x00, 0x00, 0x00, 0x09, 0x6e, 0x65, 0x74, 0x61, + 0x70, 0x70, 0x2d, 0x32, 0x36, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, request) = nfs4_req_create_session(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::CreateSession( create_ssn ) => { + assert_eq!(create_ssn.client_id, &buf[4..12]); + assert_eq!(create_ssn.seqid, 1); + assert_eq!(create_ssn.machine_name, b"netapp-26"); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_request_layoutget() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x32, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // layoutget + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x89, 0xd9, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + ]; + + let (_, stateid_buf) = nfs4_parse_stateid(&buf[40..56]).unwrap(); + assert_eq!(stateid_buf.seqid, 0); + + let (_, request) = nfs4_req_layoutget(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::LayoutGet( lyg_data ) => { + assert_eq!(lyg_data.layout_type, 1); + assert_eq!(lyg_data.min_length, 4096); + assert_eq!(lyg_data.stateid, stateid_buf); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_request_getdevinfo() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2f, /*opcode*/ + 0x01, 0x01, 0x00, 0x00, 0x00, 0xf2, 0xfa, 0x80, // getdevinfo + 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3e, 0x20, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, + ]; + + let (_, request) = nfs4_req_getdevinfo(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::GetDevInfo( getdevifo ) => { + assert_eq!(getdevifo.device_id, &buf[4..20]); + assert_eq!(getdevifo.layout_type, 1); + assert_eq!(getdevifo.maxcount, 81440); + assert_eq!(getdevifo.notify_mask, 6); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_request_layoutreturn() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x33, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // layoutreturn + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x01, 0x03, 0x82, 0x14, 0xe0, + 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, stateid_buf) = nfs4_parse_stateid(&buf[36..52]).unwrap(); + assert_eq!(stateid_buf.seqid, 1); + + let (_, request) = nfs4_req_layoutreturn(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::LayoutReturn( layoutreturn ) => { + assert_eq!(layoutreturn.layout_type, 1); + assert_eq!(layoutreturn.return_type, 1); + assert_eq!(layoutreturn.stateid, stateid_buf); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_request_destroy_session() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2c, /*opcode*/ + 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, /*ssn_id*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, + ]; + + let (_, request) = nfs4_req_destroy_session(&buf[4..]).unwrap(); + match request { + Nfs4RequestContent::DestroySession( ssn_id ) => { + assert_eq!(ssn_id, &buf[4..]); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_attrs() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x09, /*opcode*/ + 0x00, 0x00, 0x00, 0x03, /*attr_cnt*/ + 0x00, 0x00, 0x20, 0x65, /*attr_mask[0]*/ + 0x00, 0x00, 0x00, 0x00, /*attr_mask[1]*/ + 0x00, 0x00, 0x08, 0x00, /*attr_mask[2]*/ + ]; + + let (r, attr) = nfs4_parse_attrbits(&buf[4..]).unwrap(); + assert_eq!(r.len(), 0); + // assert_eq!(attr.attr_mask, 35618163785728); + assert_eq!(attr.attr_mask, 0x00002065_u64 << 32); + } + #[test] + fn test_nfs4_response_compound() { + // Operations: SEQUENCE, PUTFH, CLOSE + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x00, /*Tag*/ + 0x00, 0x00, 0x00, 0x03, /*ops_cnt*/ + // SEQUENCE + 0x00, 0x00, 0x00, 0x35, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd2, + 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0x02, 0x00, 0x00, 0x00, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, + 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, + // PUTFH + 0x00, 0x00, 0x00, 0x16, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, + // CLOSE + 0x00, 0x00, 0x00, 0x04, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + + ]; + + let sequence_buf: &[u8] = &buf[16..56]; + let putfh_buf: &[u8] = &buf[60..64]; + let close_buf: &[u8] = &buf[68..]; + + let (_, res_sequence) = nfs4_res_sequence(sequence_buf).unwrap(); + let (_, res_putfh) = nfs4_res_putfh(putfh_buf).unwrap(); + let (_, res_close) = nfs4_res_close(close_buf).unwrap(); + + let (_, compound_ops) = parse_nfs4_response_compound(buf).unwrap(); + assert_eq!(compound_ops.status, 0); + assert_eq!(compound_ops.commands[0], res_sequence); + assert_eq!(compound_ops.commands[1], res_putfh); + assert_eq!(compound_ops.commands[2], res_close); + } + + #[test] + fn test_nfs4_response_open() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x12, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + // open_data + 0x00, 0x00, 0x00, 0x01, 0x00, 0x82, 0x14, 0xe0, /*stateid*/ + 0x5b, 0x00, 0x88, 0xd9, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x16, 0xf8, 0x2f, 0xd5, /*_change_info*/ + 0xdb, 0xb7, 0xfe, 0x38, 0x16, 0xf8, 0x2f, 0xdf, + 0x21, 0xa8, 0x2a, 0x48, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, /*_attrs*/ + 0x00, 0x00, 0x00, 0x00, + // delegate_write + 0x00, 0x00, 0x00, 0x02, /*delegation_type*/ + 0x00, 0x00, 0x00, 0x01, 0x02, 0x82, 0x14, 0xe0, + 0x5b, 0x00, 0x89, 0xd9, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + ]; + + let stateid_buf = &buf[8..24]; + let (_, res_stateid) = nfs4_parse_stateid(stateid_buf).unwrap(); + + let delegate_buf = &buf[64..]; + let (_, delegate) = nfs4_parse_file_delegation(delegate_buf).unwrap(); + + let open_data_buf = &buf[8..]; + let (_, res_open_data) = nfs4_res_open_ok(open_data_buf).unwrap(); + assert_eq!(res_open_data.stateid, res_stateid); + assert_eq!(res_open_data.result_flags, 4); + assert_eq!(res_open_data.delegate, delegate); + + let (_, response) = nfs4_res_open(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::Open(status, open_data) => { + assert_eq!(status, 0); + assert_eq!(open_data, Some(res_open_data)); + } + _ => { panic!("Failure, {:?}", response); } + } + } + + #[test] + fn test_nfs4_response_write() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x26, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, /*wd*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + ]; + + let (_, wd_buf) = nfs4_res_write_ok(&buf[8..]).unwrap(); + assert_eq!(wd_buf.count, 5); + assert_eq!(wd_buf.committed, 2); + + let (_, result) = nfs4_res_write(&buf[4..]).unwrap(); + match result { + Nfs4ResponseContent::Write(status, wd) => { + assert_eq!(status, 0); + assert_eq!(wd, Some(wd_buf)); + } + _ => { panic!("Failure, {:?}", result); } + } + } + + #[test] + fn test_nfs4_response_access() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x03, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1f, /*ad*/ + ]; + + let (_, ad_buf) = nfs4_res_access_ok(&buf[8..]).unwrap(); + assert_eq!(ad_buf.supported_types, 0x1f); + assert_eq!(ad_buf.access_rights, 0x1f); + + let (_, result) = nfs4_res_access(&buf[4..]).unwrap(); + match result { + Nfs4ResponseContent::Access(status, ad) => { + assert_eq!(status, 0); + assert_eq!(ad, Some(ad_buf)); + } + _ => { panic!("Failure, {:?}", result); } + } + } + + #[test] + fn test_nfs4_response_getfh() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x0a, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x20, 0x01, 0x01, 0x00, 0x00, /*fh*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x8b, 0xae, 0xea, 0x7f, + 0xff, 0xf1, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, fh_buf) = nfs4_parse_handle(&buf[8..]).unwrap(); + + let (_, result) = nfs4_res_getfh(&buf[4..]).unwrap(); + match result { + Nfs4ResponseContent::GetFH(status, fh) => { + assert_eq!(status, 0); + assert_eq!(fh, Some(fh_buf)); + } + _ => { panic!("Failure, {:?}", result); } + } + } + + #[test] + fn test_nfs4_response_getattr() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x09, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x03, /*attr_cnt*/ + 0x00, 0x00, 0x20, 0x65, 0x00, 0x00, 0x00, 0x00, /*attr_mask*/ + 0x00, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x03, /*attrs*/ + 0xfa, 0xfe, 0xbf, 0xff, 0x60, 0xfd, 0xff, 0xfe, + 0x00, 0x00, 0x08, 0x17, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, + 0x02, 0x00, 0x10, 0x00, 0x00, 0x24, 0x40, 0x32, + 0x00, 0x00, 0x00, 0x00 + ]; + + let (_, attrs_buf) = nfs4_parse_attrs(&buf[8..]).unwrap(); + + let (_, attr_fields) = nfs4_parse_attr_fields(&buf[24..]).unwrap(); + assert_eq!(attr_fields, 48); + + let (_, result) = nfs4_res_getattr(&buf[4..]).unwrap(); + match result { + Nfs4ResponseContent::GetAttr(status, attrs) => { + assert_eq!(status, 0); + assert_eq!(attrs, Some(attrs_buf)); + } + _ => { panic!("Failure, {:?}", result); } + } + } + + #[test] + fn test_nfs4_response_readdir() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x1a, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*Status: 0*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/ + // directory_listing + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x55, 0xeb, 0x42, 0x33, /*entry0*/ + 0x00, 0x00, 0x00, 0x06, 0x43, 0x65, 0x6e, 0x74, 0x4f, 0x53, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x18, 0x09, 0x1a, 0x00, 0xb0, 0xa2, 0x3a, + 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00, 0x00, 0x02, 0xaf, 0x8f, 0x9b, 0x4e, + 0x29, 0xc4, 0xa2, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x01, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x52, 0x00, + 0xb0, 0x33, 0xf7, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x0b, + 0x00, 0x00, 0x01, 0xfd, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x12, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65, + 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65, + 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x1f, 0x17, 0xbc, 0x28, 0x86, 0x38, 0x31, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9b, 0x8f, 0xaf, 0x1d, 0xa2, 0xc4, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9b, 0x8f, 0xaf, 0x1d, 0xa2, 0xc4, 0x29, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x0b, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, /*entry1*/ + 0x00, 0x00, 0x00, 0x04, 0x64, 0x61, 0x74, 0x61, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x18, 0x09, 0x1a, 0x00, 0xb0, 0xa2, 0x3a, 0x00, 0x00, 0x00, 0xb0, + 0x00, 0x00, 0x00, 0x02, 0x83, 0x66, 0x9c, 0x4e, 0x25, 0x80, 0x82, 0x07, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x52, 0x00, 0xad, 0x37, 0xad, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x02, 0x00, 0x00, 0x03, 0xff, + 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x10, 0x72, 0x6f, 0x6f, 0x74, + 0x40, 0x66, 0x69, 0x61, 0x6e, 0x65, 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, + 0x00, 0x00, 0x00, 0x10, 0x72, 0x6f, 0x6f, 0x74, 0x40, 0x66, 0x69, 0x61, + 0x6e, 0x65, 0x2e, 0x69, 0x6e, 0x74, 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x4d, 0x6a, 0x97, 0xdb, 0x33, 0x89, 0xba, 0x2d, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9c, 0x66, 0x83, 0x07, 0x82, 0x80, 0x25, + 0x00, 0x00, 0x00, 0x00, 0x4e, 0x9c, 0x66, 0x83, 0x07, 0x82, 0x80, 0x25, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, /*value_follows*/ + 0x00, 0x00, 0x00, 0x01, /*EOF: YES*/ + ]; + + let entry0_buf = &buf[16..240]; + let entry1_buf = &buf[240..452]; + + let (_, res_entry0) = nfs4_res_readdir_entry_do(&entry0_buf[4..]).unwrap(); + assert_eq!(res_entry0.name, "CentOS".as_bytes()); + + let (_, res_entry1) = nfs4_res_readdir_entry_do(&entry1_buf[4..]).unwrap(); + assert_eq!(res_entry1.name, "data".as_bytes()); + + let (_, res_rd) = nfs4_res_readdir_ok(&buf[8..]).unwrap(); + assert!(res_rd.eof); + assert_eq!(res_rd.listing, [Some(res_entry0), Some(res_entry1)]); + + let (_, response) = nfs4_res_readdir(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::ReadDir(status, rd) => { + assert_eq!(status, 0); + assert_eq!(rd, Some(res_rd)); + } + _ => { panic!("Failure!"); } + } + } + + #[test] + fn test_nfs4_response_setclientid() { + #[rustfmt::skip] + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x23, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x14, 0x67, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x01, /*_clientid*/ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /*_verifier*/ + ]; + + let (_, response) = nfs4_res_setclientid(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::SetClientId(status) => { + assert_eq!(status, 0); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_response_exchangeid() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2a, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + // exchange_id + 0xe0, 0x14, 0x82, 0x00, 0x00, 0x00, 0x02, 0xd2, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x98, 0x3b, 0xa3, 0x1e, + 0xd7, 0xa9, 0x11, 0xe8, 0x00, 0x00, 0x00, 0x10, + 0x98, 0x3b, 0xa3, 0x1e, 0xd7, 0xa9, 0x11, 0xe8, + 0xbc, 0x0c, 0x00, 0x0c, 0x29, 0xe9, 0x13, 0x93, + 0x00, 0x00, 0x00, 0x10, 0x84, 0x8b, 0x93, 0x12, + 0xd7, 0xa9, 0x11, 0xe8, 0xbc, 0x0c, 0x00, 0x0c, + 0x29, 0xe9, 0x13, 0x93, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x0c, 0x6e, 0x65, 0x74, 0x61, + 0x70, 0x70, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x24, 0x4e, 0x65, 0x74, 0x41, + 0x70, 0x70, 0x20, 0x52, 0x65, 0x6c, 0x65, 0x61, + 0x73, 0x65, 0x20, 0x56, 0x6f, 0x6f, 0x64, 0x6f, + 0x6f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x72, 0x5f, + 0x5f, 0x39, 0x2e, 0x36, 0x2e, 0x30, 0x00, 0x00, + 0x26, 0x0d, 0xcf, 0x5b, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, xchangeid) = nfs4_parse_res_exchangeid(&buf[8..]).unwrap(); + + let (_, response) = nfs4_res_exchangeid(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::ExchangeId(status, xchngid_data) => { + assert_eq!(status, 0); + assert_eq!(xchngid_data, Some(xchangeid)); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_response_create_session() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2b, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + // create_session + 0x00, 0x00, 0x02, 0xd2, 0xe0, 0x14, 0x82, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x18, 0x00, + 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x02, 0x80, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, create_ssn) = nfs4_parse_res_create_session(&buf[8..]).unwrap(); + + let (_, response) = nfs4_res_create_session(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::CreateSession( status, create_ssn_data) => { + assert_eq!(status, 0); + assert_eq!(create_ssn_data, Some(create_ssn)); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_response_layoutget() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x32, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + // layoutget + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x03, 0x82, 0x14, 0xe0, 0x5b, 0x00, 0x89, 0xd9, + 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x58, 0x01, 0x01, 0x00, 0x00, + 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x30, 0x01, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x84, 0x72, 0x00, 0x00, 0x23, 0xa6, 0xc0, 0x12, + 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, + 0x00, 0xf2, 0xfa, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x00, 0x00, 0x00, + ]; + + let (_, stateid) = nfs4_parse_stateid(&buf[12..28]).unwrap(); + + let (_, lyg_data) = nfs4_parse_res_layoutget(&buf[8..]).unwrap(); + assert_eq!(lyg_data.stateid, stateid); + assert_eq!(lyg_data.layout_type, 1); + assert_eq!(lyg_data.device_id, &buf[60..76]); + + let (_, response) = nfs4_res_layoutget(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::LayoutGet( status, lyg ) => { + assert_eq!(status, 0); + assert_eq!(lyg, Some(lyg_data)); + } + _ => { panic!("Failure"); } + } + } + + #[test] + fn test_nfs4_response_getdevinfo() { + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2f, /*opcode*/ + 0x00, 0x00, 0x00, 0x00, /*status*/ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, // getdevinfo + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x74, 0x63, 0x70, 0x00, + 0x00, 0x00, 0x00, 0x10, 0x31, 0x39, 0x32, 0x2e, + 0x31, 0x36, 0x38, 0x2e, 0x30, 0x2e, 0x36, 0x31, + 0x2e, 0x38, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, + ]; + + let (_, getdevinfo) = nfs4_parse_res_getdevinfo(&buf[8..]).unwrap(); + assert_eq!(getdevinfo.layout_type, 1); + assert_eq!(getdevinfo.r_netid, b"tcp"); + assert_eq!(getdevinfo.r_addr, b"192.168.0.61.8.1"); + + let (_, response) = nfs4_res_getdevinfo(&buf[4..]).unwrap(); + match response { + Nfs4ResponseContent::GetDevInfo(status, getdevinfo_data) => { + assert_eq!(status, 0); + assert_eq!(getdevinfo_data, Some(getdevinfo)) + } + _ => { panic!("Failure"); } + } + } +} diff --git a/rust/src/nfs/nfs_records.rs b/rust/src/nfs/nfs_records.rs new file mode 100644 index 0000000..3b0804b --- /dev/null +++ b/rust/src/nfs/nfs_records.rs @@ -0,0 +1,29 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for NFS + +#[derive(Debug,PartialEq, Eq)] +pub struct NfsReplyRead<'a> { + pub status: u32, + pub attr_follows: u32, + pub attr_blob: &'a[u8], + pub count: u32, + pub eof: bool, + pub data_len: u32, + pub data: &'a[u8], // likely partial +} diff --git a/rust/src/nfs/rpc_records.rs b/rust/src/nfs/rpc_records.rs new file mode 100644 index 0000000..d373ae9 --- /dev/null +++ b/rust/src/nfs/rpc_records.rs @@ -0,0 +1,465 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Nom parsers for RPCv2 + +use crate::common::nom7::bits; +use nom7::bits::streaming::take as take_bits; +use nom7::bytes::streaming::take; +use nom7::combinator::{cond, verify, rest}; +use nom7::multi::length_data; +use nom7::number::streaming::{be_u32}; +use nom7::sequence::tuple; +use nom7::error::{make_error, ErrorKind}; +use nom7::{IResult, Err}; + +pub const RPC_MAX_MACHINE_SIZE: u32 = 256; // Linux kernel defines 64. +pub const RPC_MAX_CREDS_SIZE: u32 = 4096; // Linux kernel defines 400. +pub const RPC_MAX_VERIFIER_SIZE: u32 = 4096; // Linux kernel defines 400. + +#[derive(Debug, PartialEq, Eq)] +pub enum RpcRequestCreds<'a> { + Unix(RpcRequestCredsUnix<'a>), + GssApi(RpcRequestCredsGssApi<'a>), + Unknown(&'a [u8]), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcRequestCredsUnix<'a> { + pub stamp: u32, + pub machine_name_len: u32, + pub machine_name_buf: &'a [u8], + pub uid: u32, + pub gid: u32, + pub aux_gids: Option<Vec<u32>>, + // list of gids +} + +//named!(parse_rpc_creds_unix_aux_gids<Vec<u32>>, +// many0!(be_u32) +//); + +fn parse_rpc_request_creds_unix(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> { + let (i, stamp) = be_u32(i)?; + let (i, machine_name_len) = verify(be_u32, |&size| size < RPC_MAX_MACHINE_SIZE)(i)?; + let (i, machine_name_buf) = take(machine_name_len as usize)(i)?; + let (i, uid) = be_u32(i)?; + let (i, gid) = be_u32(i)?; + // let (i, aux_gids) = parse_rpc_creds_unix_aux_gids(i)?; + let creds = RpcRequestCreds::Unix(RpcRequestCredsUnix { + stamp, + machine_name_len, + machine_name_buf, + uid, + gid, + aux_gids: None, + }); + Ok((i, creds)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcRequestCredsGssApi<'a> { + pub version: u32, + pub procedure: u32, + pub seq_num: u32, + pub service: u32, + + pub ctx: &'a [u8], +} + +fn parse_rpc_request_creds_gssapi(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> { + let (i, version) = be_u32(i)?; + let (i, procedure) = be_u32(i)?; + let (i, seq_num) = be_u32(i)?; + let (i, service) = be_u32(i)?; + let (i, ctx) = length_data(be_u32)(i)?; + let creds = RpcRequestCreds::GssApi(RpcRequestCredsGssApi { + version, + procedure, + seq_num, + service, + ctx, + }); + Ok((i, creds)) +} + +fn parse_rpc_request_creds_unknown(i: &[u8]) -> IResult<&[u8], RpcRequestCreds> { + Ok((&[], RpcRequestCreds::Unknown(i))) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcGssApiIntegrity<'a> { + pub seq_num: u32, + pub data: &'a [u8], +} + +// Parse the GSSAPI Integrity envelope to get to the +// data we care about. +pub fn parse_rpc_gssapi_integrity(i: &[u8]) -> IResult<&[u8], RpcGssApiIntegrity> { + let (i, len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?; + let (i, seq_num) = be_u32(i)?; + let (i, data) = take(len as usize)(i)?; + let res = RpcGssApiIntegrity { seq_num, data }; + Ok((i, res)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcPacketHeader { + pub frag_is_last: bool, + pub frag_len: u32, + pub xid: u32, + pub msgtype: u32, +} + +fn parse_bits(i: &[u8]) -> IResult<&[u8], (u8, u32)> { + bits(tuple(( + take_bits(1u8), // is_last + take_bits(31u32), // len + )))(i) +} + +pub fn parse_rpc_packet_header(i: &[u8]) -> IResult<&[u8], RpcPacketHeader> { + let (i, fraghdr) = verify(parse_bits, |v: &(u8,u32)| v.1 >= 24)(i)?; + let (i, xid) = be_u32(i)?; + let (i, msgtype) = verify(be_u32, |&v| v <= 1)(i)?; + let hdr = RpcPacketHeader { + frag_is_last: fraghdr.0 == 1, + frag_len: fraghdr.1, + xid, + msgtype, + }; + Ok((i, hdr)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcReplyPacket<'a> { + pub hdr: RpcPacketHeader, + + pub verifier_flavor: u32, + pub verifier_len: u32, + pub verifier: Option<&'a [u8]>, + + pub reply_state: u32, + pub accept_state: u32, + + pub prog_data_size: u32, + pub prog_data: &'a [u8], +} + +// top of request packet, just to get to procedure +#[derive(Debug, PartialEq, Eq)] +pub struct RpcRequestPacketPartial { + pub hdr: RpcPacketHeader, + + pub rpcver: u32, + pub program: u32, + pub progver: u32, + pub procedure: u32, +} + +pub fn parse_rpc_request_partial(i: &[u8]) -> IResult<&[u8], RpcRequestPacketPartial> { + let (i, hdr) = parse_rpc_packet_header(i)?; + let (i, rpcver) = be_u32(i)?; + let (i, program) = be_u32(i)?; + let (i, progver) = be_u32(i)?; + let (i, procedure) = be_u32(i)?; + let req = RpcRequestPacketPartial { + hdr, + rpcver, + program, + progver, + procedure, + }; + Ok((i, req)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RpcPacket<'a> { + pub hdr: RpcPacketHeader, + + pub rpcver: u32, + pub program: u32, + pub progver: u32, + pub procedure: u32, + + pub creds_flavor: u32, + pub creds_len: u32, + pub creds: RpcRequestCreds<'a>, + + pub verifier_flavor: u32, + pub verifier_len: u32, + pub verifier: &'a [u8], + + pub prog_data_size: u32, + pub prog_data: &'a [u8], +} + +/// Parse request RPC record. +/// +/// Can be called from 2 paths: +/// 1. we have all data -> do full parsing +/// 2. we have partial data (large records) -> allow partial prog_data parsing +/// +/// Arguments: +/// * `complete`: +/// type: bool +/// description: do full parsing, including of `prog_data` +/// +pub fn parse_rpc(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcPacket> { + let (i, hdr) = parse_rpc_packet_header(start_i)?; + let rec_size = hdr.frag_len as usize + 4; + + let (i, rpcver) = be_u32(i)?; + let (i, program) = be_u32(i)?; + let (i, progver) = be_u32(i)?; + let (i, procedure) = be_u32(i)?; + + let (i, creds_flavor) = be_u32(i)?; + let (i, creds_len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?; + let (i, creds_buf) = take(creds_len as usize)(i)?; + let (_, creds) = match creds_flavor { + 1 => parse_rpc_request_creds_unix(creds_buf)?, + 6 => parse_rpc_request_creds_gssapi(creds_buf)?, + _ => parse_rpc_request_creds_unknown(creds_buf)?, + }; + + let (i, verifier_flavor) = be_u32(i)?; + let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?; + let (i, verifier) = take(verifier_len as usize)(i)?; + + let consumed = start_i.len() - i.len(); + if consumed > rec_size { + return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); + } + + let data_size : u32 = (rec_size - consumed) as u32; + let (i, prog_data) = if !complete { + rest(i)? + } else { + take(data_size)(i)? + }; + + let packet = RpcPacket { + hdr, + + rpcver, + program, + progver, + procedure, + + creds_flavor, + creds_len, + creds, + + verifier_flavor, + verifier_len, + verifier, + + prog_data_size: data_size, + prog_data, + }; + Ok((i, packet)) +} + +/// Parse reply RPC record. +/// +/// Can be called from 2 paths: +/// 1. we have all data -> do full parsing +/// 2. we have partial data (large records) -> allow partial prog_data parsing +/// +/// Arguments: +/// * `complete`: +/// type: bool +/// description: do full parsing, including of `prog_data` +/// +pub fn parse_rpc_reply(start_i: &[u8], complete: bool) -> IResult<&[u8], RpcReplyPacket> { + let (i, hdr) = parse_rpc_packet_header(start_i)?; + let rec_size = hdr.frag_len + 4; + + let (i, reply_state) = verify(be_u32, |&v| v <= 1)(i)?; + + let (i, verifier_flavor) = be_u32(i)?; + let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?; + let (i, verifier) = cond(verifier_len > 0, take(verifier_len as usize))(i)?; + + let (i, accept_state) = be_u32(i)?; + + let consumed = start_i.len() - i.len(); + if consumed > rec_size as usize { + return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); + } + + let data_size : u32 = (rec_size as usize - consumed) as u32; + let (i, prog_data) = if !complete { + rest(i)? + } else { + take(data_size)(i)? + }; + let packet = RpcReplyPacket { + hdr, + + verifier_flavor, + verifier_len, + verifier, + + reply_state, + accept_state, + + prog_data_size: data_size, + prog_data, + }; + Ok((i, packet)) +} + +pub fn parse_rpc_udp_packet_header(i: &[u8]) -> IResult<&[u8], RpcPacketHeader> { + let (i, xid) = be_u32(i)?; + let (i, msgtype) = verify(be_u32, |&v| v <= 1)(i)?; + let hdr = RpcPacketHeader { + frag_is_last: false, + frag_len: 0, + + xid, + msgtype, + }; + Ok((i, hdr)) +} + +pub fn parse_rpc_udp_request(i: &[u8]) -> IResult<&[u8], RpcPacket> { + let (i, hdr) = parse_rpc_udp_packet_header(i)?; + + let (i, rpcver) = be_u32(i)?; + let (i, program) = be_u32(i)?; + let (i, progver) = be_u32(i)?; + let (i, procedure) = be_u32(i)?; + + let (i, creds_flavor) = be_u32(i)?; + let (i, creds_len) = verify(be_u32, |&size| size < RPC_MAX_CREDS_SIZE)(i)?; + let (i, creds_buf) = take(creds_len as usize)(i)?; + let (_, creds) = match creds_flavor { + 1 => parse_rpc_request_creds_unix(creds_buf)?, + 6 => parse_rpc_request_creds_gssapi(creds_buf)?, + _ => parse_rpc_request_creds_unknown(creds_buf)?, + }; + + let (i, verifier_flavor) = be_u32(i)?; + let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?; + let (i, verifier) = take(verifier_len as usize)(i)?; + + let data_size : u32 = i.len() as u32; + let (i, prog_data) = rest(i)?; + let packet = RpcPacket { + hdr, + + rpcver, + program, + progver, + procedure, + + creds_flavor, + creds_len, + creds, + + verifier_flavor, + verifier_len, + verifier, + + prog_data_size: data_size, + prog_data, + }; + Ok((i, packet)) +} + +pub fn parse_rpc_udp_reply(i: &[u8]) -> IResult<&[u8], RpcReplyPacket> { + let (i, hdr) = parse_rpc_udp_packet_header(i)?; + + let (i, verifier_flavor) = be_u32(i)?; + let (i, verifier_len) = verify(be_u32, |&size| size < RPC_MAX_VERIFIER_SIZE)(i)?; + let (i, verifier) = cond(verifier_len > 0, take(verifier_len as usize))(i)?; + + let (i, reply_state) = verify(be_u32, |&v| v <= 1)(i)?; + let (i, accept_state) = be_u32(i)?; + + let data_size : u32 = i.len() as u32; + let (i, prog_data) = rest(i)?; + let packet = RpcReplyPacket { + hdr, + + verifier_flavor, + verifier_len, + verifier, + + reply_state, + accept_state, + + prog_data_size: data_size, + prog_data, + }; + Ok((i, packet)) +} + +#[cfg(test)] +mod tests { + use crate::nfs::rpc_records::*; + use nom7::Err::Incomplete; + use nom7::Needed; + + #[test] + fn test_partial_input_too_short() { + let buf: &[u8] = &[ + 0x80, 0x00, 0x00, 0x9c, // flags + 0x8e, 0x28, 0x02, 0x7e // xid + ]; + + let r = parse_rpc_request_partial(buf); + match r { + Err(Incomplete(s)) => { assert_eq!(s, Needed::new(4)); }, + _ => { panic!("failed {:?}",r); } + } + } + #[test] + fn test_partial_input_ok() { + let buf: &[u8] = &[ + 0x80, 0x00, 0x00, 0x9c, // flags + 0x8e, 0x28, 0x02, 0x7e, // xid + 0x00, 0x00, 0x00, 0x01, // msgtype + 0x00, 0x00, 0x00, 0x02, // rpcver + 0x00, 0x00, 0x00, 0x03, // program + 0x00, 0x00, 0x00, 0x04, // progver + 0x00, 0x00, 0x00, 0x05, // procedure + ]; + let expected = RpcRequestPacketPartial { + hdr: RpcPacketHeader { + frag_is_last: true, + frag_len: 156, + xid: 2384986750, + msgtype: 1 + }, + rpcver: 2, + program: 3, + progver: 4, + procedure: 5 + }; + let r = parse_rpc_request_partial(buf); + match r { + Ok((rem, hdr)) => { + assert_eq!(rem.len(), 0); + assert_eq!(hdr, expected); + }, + _ => { panic!("failed {:?}",r); } + } + } +} diff --git a/rust/src/nfs/types.rs b/rust/src/nfs/types.rs new file mode 100644 index 0000000..96195b9 --- /dev/null +++ b/rust/src/nfs/types.rs @@ -0,0 +1,293 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* RFC 1813, section '3. Server Procedures' */ +pub const NFSPROC3_NULL: u32 = 0; +pub const NFSPROC3_GETATTR: u32 = 1; +pub const NFSPROC3_SETATTR: u32 = 2; +pub const NFSPROC3_LOOKUP: u32 = 3; +pub const NFSPROC3_ACCESS: u32 = 4; +pub const NFSPROC3_READLINK: u32 = 5; +pub const NFSPROC3_READ: u32 = 6; +pub const NFSPROC3_WRITE: u32 = 7; +pub const NFSPROC3_CREATE: u32 = 8; +pub const NFSPROC3_MKDIR: u32 = 9; +pub const NFSPROC3_SYMLINK: u32 = 10; +pub const NFSPROC3_MKNOD: u32 = 11; +pub const NFSPROC3_REMOVE: u32 = 12; +pub const NFSPROC3_RMDIR: u32 = 13; +pub const NFSPROC3_RENAME: u32 = 14; +pub const NFSPROC3_LINK: u32 = 15; +pub const NFSPROC3_READDIR: u32 = 16; +pub const NFSPROC3_READDIRPLUS: u32 = 17; +pub const NFSPROC3_FSSTAT: u32 = 18; +pub const NFSPROC3_FSINFO: u32 = 19; +pub const NFSPROC3_PATHCONF: u32 = 20; +pub const NFSPROC3_COMMIT: u32 = 21; + +pub fn nfs3_procedure_string(procedure: u32) -> String { + match procedure { + NFSPROC3_NULL => "NULL", + NFSPROC3_GETATTR => "GETATTR", + NFSPROC3_SETATTR => "SETATTR", + NFSPROC3_LOOKUP => "LOOKUP", + NFSPROC3_ACCESS => "ACCESS", + NFSPROC3_READLINK => "READLINK", + NFSPROC3_READ => "READ", + NFSPROC3_WRITE => "WRITE", + NFSPROC3_CREATE => "CREATE", + NFSPROC3_MKDIR => "MKDIR", + NFSPROC3_SYMLINK => "SYMLINK", + NFSPROC3_MKNOD => "MKNOD", + NFSPROC3_REMOVE => "REMOVE", + NFSPROC3_RMDIR => "RMDIR", + NFSPROC3_RENAME => "RENAME", + NFSPROC3_LINK => "LINK", + NFSPROC3_READDIR => "READDIR", + NFSPROC3_READDIRPLUS => "READDIRPLUS", + NFSPROC3_FSSTAT => "FSSTAT", + NFSPROC3_FSINFO => "FSINFO", + NFSPROC3_PATHCONF => "PATHCONF", + NFSPROC3_COMMIT => "COMMIT", + _ => { + return (procedure).to_string(); + } + }.to_string() +} + +/* RFC 1813, section '2.6 Defined Error Numbers' */ +pub const NFS3_OK: u32 = 0; +pub const NFS3ERR_PERM: u32 = 1; +pub const NFS3ERR_NOENT: u32 = 2; +pub const NFS3ERR_IO: u32 = 5; +pub const NFS3ERR_NXIO: u32 = 6; +pub const NFS3ERR_ACCES: u32 = 13; +pub const NFS3ERR_EXIST: u32 = 17; +pub const NFS3ERR_XDEV: u32 = 18; +pub const NFS3ERR_NODEV: u32 = 19; +pub const NFS3ERR_NOTDIR: u32 = 20; +pub const NFS3ERR_ISDIR: u32 = 21; +pub const NFS3ERR_INVAL: u32 = 22; +pub const NFS3ERR_FBIG: u32 = 27; +pub const NFS3ERR_NOSPC: u32 = 28; +pub const NFS3ERR_ROFS: u32 = 30; +pub const NFS3ERR_MLINK: u32 = 31; +pub const NFS3ERR_NAMETOOLONG: u32 = 63; +pub const NFS3ERR_NOTEMPTY: u32 = 66; +pub const NFS3ERR_DQUOT: u32 = 69; +pub const NFS3ERR_STALE: u32 = 70; +pub const NFS3ERR_REMOTE: u32 = 71; +pub const NFS3ERR_BADHANDLE: u32 = 10001; +pub const NFS3ERR_NOT_SYNC: u32 = 10002; +pub const NFS3ERR_BAD_COOKIE: u32 = 10003; +pub const NFS3ERR_NOTSUPP: u32 = 10004; +pub const NFS3ERR_TOOSMALL: u32 = 10005; +pub const NFS3ERR_SERVERFAULT: u32 = 10006; +pub const NFS3ERR_BADTYPE: u32 = 10007; +pub const NFS3ERR_JUKEBOX: u32 = 10008; + +pub fn nfs3_status_string(status: u32) -> String { + match status { + NFS3_OK => "OK", + NFS3ERR_PERM => "ERR_PERM", + NFS3ERR_NOENT => "ERR_NOENT", + NFS3ERR_IO => "ERR_IO", + NFS3ERR_NXIO => "ERR_NXIO", + NFS3ERR_ACCES => "ERR_ACCES", + NFS3ERR_EXIST => "ERR_EXIST", + NFS3ERR_XDEV => "ERR_XDEV", + NFS3ERR_NODEV => "ERR_NODEV", + NFS3ERR_NOTDIR => "ERR_NOTDIR", + NFS3ERR_ISDIR => "ERR_ISDIR", + NFS3ERR_INVAL => "ERR_INVAL", + NFS3ERR_FBIG => "ERR_FBIG", + NFS3ERR_NOSPC => "ERR_NOSPC", + NFS3ERR_ROFS => "ERR_ROFS", + NFS3ERR_MLINK => "ERR_MLINK", + NFS3ERR_NAMETOOLONG => "ERR_NAMETOOLONG", + NFS3ERR_NOTEMPTY => "ERR_NOTEMPTY", + NFS3ERR_DQUOT => "ERR_DQUOT", + NFS3ERR_STALE => "ERR_STALE", + NFS3ERR_REMOTE => "ERR_REMOTE", + NFS3ERR_BADHANDLE => "ERR_BADHANDLE", + NFS3ERR_NOT_SYNC => "ERR_NOT_SYNC", + NFS3ERR_BAD_COOKIE => "ERR_BAD_COOKIE", + NFS3ERR_NOTSUPP => "ERR_NOTSUPP", + NFS3ERR_TOOSMALL => "ERR_TOOSMALL", + NFS3ERR_SERVERFAULT => "ERR_SERVERFAULT", + NFS3ERR_BADTYPE => "ERR_BADTYPE", + NFS3ERR_JUKEBOX => "ERR_JUKEBOX", + _ => { + return (status).to_string(); + }, + }.to_string() +} + +pub const RPCMSG_ACCEPTED: u32 = 0; +pub const RPCMSG_DENIED: u32 = 1; + +pub fn rpc_status_string(status: u32) -> String { + match status { + RPCMSG_ACCEPTED => "ACCEPTED", + RPCMSG_DENIED => "DENIED", + _ => { + return (status).to_string(); + }, + }.to_string() +} + +/* http://www.iana.org/assignments/rpc-authentication-numbers/rpc-authentication-numbers.xhtml */ +/* RFC 1057 Section 7.2 */ +/* RFC 2203 Section 3 */ + +pub const RPCAUTH_NULL: u32 = 0; +pub const RPCAUTH_UNIX: u32 = 1; +pub const RPCAUTH_SHORT: u32 = 2; +pub const RPCAUTH_DH: u32 = 3; +pub const RPCAUTH_KERB: u32 = 4; +pub const RPCAUTH_RSA: u32 = 5; +pub const RPCAUTH_GSS: u32 = 6; + +pub fn rpc_auth_type_string(auth_type: u32) -> String { + match auth_type { + RPCAUTH_NULL => "NULL", + RPCAUTH_UNIX => "UNIX", + RPCAUTH_SHORT => "SHORT", + RPCAUTH_DH => "DH", + RPCAUTH_KERB => "KERB", + RPCAUTH_RSA => "RSA", + RPCAUTH_GSS => "GSS", + _ => { + return (auth_type).to_string(); + }, + }.to_string() +} + +pub fn rpc_auth_type_known(auth_type: u32) -> i8 { + // RPCAUTH_GSS is the maximum + if auth_type <= RPCAUTH_GSS { + return 1; + } + return -1; +} + + +pub const NFSPROC4_NULL: u32 = 0; +pub const NFSPROC4_COMPOUND: u32 = 1; +/* ops */ +pub const NFSPROC4_ACCESS: u32 = 3; +pub const NFSPROC4_CLOSE: u32 = 4; +pub const NFSPROC4_COMMIT: u32 = 5; +pub const NFSPROC4_CREATE: u32 = 6; +pub const NFSPROC4_DELEGPURGE: u32 = 7; +pub const NFSPROC4_DELEGRETURN: u32 = 8; +pub const NFSPROC4_GETATTR: u32 = 9; +pub const NFSPROC4_GETFH: u32 = 10; +pub const NFSPROC4_LINK: u32 = 11; +pub const NFSPROC4_LOCK: u32 = 12; +pub const NFSPROC4_LOCKT: u32 = 13; +pub const NFSPROC4_LOCKU: u32 = 14; +pub const NFSPROC4_LOOKUP: u32 = 15; +pub const NFSPROC4_LOOKUPP: u32 = 16; +pub const NFSPROC4_NVERIFY: u32 = 17; +pub const NFSPROC4_OPEN: u32 = 18; +pub const NFSPROC4_OPENATTR: u32 = 19; +pub const NFSPROC4_OPEN_CONFIRM: u32 = 20; +pub const NFSPROC4_OPEN_DOWNGRADE: u32 = 21; +pub const NFSPROC4_PUTFH: u32 = 22; +pub const NFSPROC4_PUTPUBFH: u32 = 23; +pub const NFSPROC4_PUTROOTFH: u32 = 24; +pub const NFSPROC4_READ: u32 = 25; +pub const NFSPROC4_READDIR: u32 = 26; +pub const NFSPROC4_READLINK: u32 = 27; +pub const NFSPROC4_REMOVE: u32 = 28; +pub const NFSPROC4_RENAME: u32 = 29; +pub const NFSPROC4_RENEW: u32 = 30; +pub const NFSPROC4_RESTOREFH: u32 = 31; +pub const NFSPROC4_SAVEFH: u32 = 32; +pub const NFSPROC4_SECINFO: u32 = 33; +pub const NFSPROC4_SETATTR: u32 = 34; +pub const NFSPROC4_SETCLIENTID: u32 = 35; +pub const NFSPROC4_SETCLIENTID_CONFIRM: u32 = 36; +pub const NFSPROC4_VERIFY: u32 = 37; +pub const NFSPROC4_WRITE: u32 = 38; +pub const NFSPROC4_RELEASE_LOCKOWNER: u32 = 39; +pub const NFSPROC4_EXCHANGE_ID: u32 = 42; +pub const NFSPROC4_CREATE_SESSION: u32 = 43; +pub const NFSPROC4_DESTROY_SESSION: u32 = 44; +pub const NFSPROC4_GETDEVINFO: u32 = 47; +pub const NFSPROC4_LAYOUTGET: u32 = 50; +pub const NFSPROC4_LAYOUTRETURN: u32 = 51; +pub const NFSPROC4_SECINFO_NO_NAME: u32 = 52; +pub const NFSPROC4_SEQUENCE: u32 = 53; +pub const NFSPROC4_DESTROY_CLIENTID: u32 = 57; +pub const NFSPROC4_RECLAIM_COMPLETE: u32 = 58; + +pub const NFSPROC4_ILLEGAL: u32 = 10044; + + +pub fn nfs4_procedure_string(procedure: u32) -> String { + match procedure { + NFSPROC4_COMPOUND => "COMPOUND", + NFSPROC4_NULL => "NULL", + // ops + NFSPROC4_ACCESS => "ACCESS", + NFSPROC4_CLOSE => "CLOSE", + NFSPROC4_COMMIT => "COMMIT", + NFSPROC4_CREATE => "CREATE", + NFSPROC4_DELEGPURGE => "DELEGPURGE", + NFSPROC4_DELEGRETURN => "DELEGRETURN", + NFSPROC4_GETATTR => "GETATTR", + NFSPROC4_GETFH => "GETFH", + NFSPROC4_LINK => "LINK", + NFSPROC4_LOCK => "LOCK", + NFSPROC4_LOCKT => "LOCKT", + NFSPROC4_LOCKU => "LOCKU", + NFSPROC4_LOOKUP => "LOOKUP", + NFSPROC4_LOOKUPP => "LOOKUPP", + NFSPROC4_NVERIFY => "NVERIFY", + NFSPROC4_OPEN => "OPEN", + NFSPROC4_OPENATTR => "OPENATTR", + NFSPROC4_OPEN_CONFIRM => "OPEN_CONFIRM", + NFSPROC4_OPEN_DOWNGRADE => "OPEN_DOWNGRADE", + NFSPROC4_PUTFH => "PUTFH", + NFSPROC4_PUTPUBFH => "PUTPUBFH", + NFSPROC4_PUTROOTFH => "PUTROOTFH", + NFSPROC4_READ => "READ", + NFSPROC4_READDIR => "READDIR", + NFSPROC4_READLINK => "READLINK", + NFSPROC4_REMOVE => "REMOVE", + NFSPROC4_RENAME => "RENAME", + NFSPROC4_RENEW => "RENEW", + NFSPROC4_RESTOREFH => "RESTOREFH", + NFSPROC4_SAVEFH => "SAVEFH", + NFSPROC4_SECINFO => "SECINFO", + NFSPROC4_SETATTR => "SETATTR", + NFSPROC4_SETCLIENTID => "SETCLIENTID", + NFSPROC4_SETCLIENTID_CONFIRM => "SETCLIENTID_CONFIRM", + NFSPROC4_VERIFY => "VERIFY", + NFSPROC4_WRITE => "WRITE", + NFSPROC4_RELEASE_LOCKOWNER => "RELEASE_LOCKOWNER", + NFSPROC4_ILLEGAL => "ILLEGAL", + _ => { + return (procedure).to_string(); + } + }.to_string() +} + +pub const NFS4_OK: u32 = 0; + diff --git a/rust/src/ntp/mod.rs b/rust/src/ntp/mod.rs new file mode 100644 index 0000000..30ff834 --- /dev/null +++ b/rust/src/ntp/mod.rs @@ -0,0 +1,22 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! NTP application layer and parser module. + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +pub mod ntp; diff --git a/rust/src/ntp/ntp.rs b/rust/src/ntp/ntp.rs new file mode 100644 index 0000000..c7b3b3d --- /dev/null +++ b/rust/src/ntp/ntp.rs @@ -0,0 +1,338 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +extern crate ntp_parser; +use self::ntp_parser::*; +use crate::core; +use crate::core::{AppProto,Flow,ALPROTO_UNKNOWN,ALPROTO_FAILED,Direction}; +use crate::applayer::{self, *}; +use std; +use std::ffi::CString; + +use nom7::Err; + +#[derive(AppLayerEvent)] +pub enum NTPEvent { + UnsolicitedResponse , + MalformedData, + NotRequest, + NotResponse, +} + +#[derive(Default)] +pub struct NTPState { + state_data: AppLayerStateData, + + /// List of transactions for this session + transactions: Vec<NTPTransaction>, + + /// Events counter + events: u16, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, +} + +#[derive(Debug, Default)] +pub struct NTPTransaction { + /// The NTP reference ID + pub xid: u32, + + /// The internal transaction id + id: u64, + + tx_data: applayer::AppLayerTxData, +} + +impl Transaction for NTPTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl NTPState { + pub fn new() -> Self { + Default::default() + } +} + +impl State<NTPTransaction> for NTPState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&NTPTransaction> { + self.transactions.get(index) + } +} + +impl NTPState { + /// Parse an NTP request message + /// + /// Returns 0 if successful, or -1 on error + fn parse(&mut self, i: &[u8], direction: Direction) -> i32 { + match parse_ntp(i) { + Ok((_,ref msg)) => { + // SCLogDebug!("parse_ntp: {:?}",msg); + let (mode, ref_id) = match msg { + NtpPacket::V3(pkt) => (pkt.mode, pkt.ref_id), + NtpPacket::V4(pkt) => (pkt.mode, pkt.ref_id), + }; + if mode == NtpMode::SymmetricActive || mode == NtpMode::Client { + let mut tx = self.new_tx(direction); + // use the reference id as identifier + tx.xid = ref_id; + self.transactions.push(tx); + } + 0 + }, + Err(Err::Incomplete(_)) => { + SCLogDebug!("Insufficient data while parsing NTP data"); + self.set_event(NTPEvent::MalformedData); + -1 + }, + Err(_) => { + SCLogDebug!("Error while parsing NTP data"); + self.set_event(NTPEvent::MalformedData); + -1 + }, + } + } + + fn free(&mut self) { + // All transactions are freed when the `transactions` object is freed. + // But let's be explicit + self.transactions.clear(); + } + + fn new_tx(&mut self, direction: Direction) -> NTPTransaction { + self.tx_id += 1; + NTPTransaction::new(direction, self.tx_id) + } + + pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&NTPTransaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: NTPEvent) { + if let Some(tx) = self.transactions.last_mut() { + tx.tx_data.set_event(event as u8); + self.events += 1; + } + } +} + +impl NTPTransaction { + pub fn new(direction: Direction, id: u64) -> NTPTransaction { + NTPTransaction { + xid: 0, + id, + tx_data: applayer::AppLayerTxData::for_direction(direction), + } + } +} + +/// Returns *mut NTPState +#[no_mangle] +pub extern "C" fn rs_ntp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = NTPState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut NTPState as void pointer +#[no_mangle] +pub extern "C" fn rs_ntp_state_free(state: *mut std::os::raw::c_void) { + let mut ntp_state = unsafe{ Box::from_raw(state as *mut NTPState) }; + ntp_state.free(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ntp_parse_request(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,NTPState); + if state.parse(stream_slice.as_slice(), Direction::ToServer) < 0 { + return AppLayerResult::err(); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ntp_parse_response(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,NTPState); + if state.parse(stream_slice.as_slice(), Direction::ToClient) < 0 { + return AppLayerResult::err(); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ntp_state_get_tx(state: *mut std::os::raw::c_void, + tx_id: u64) + -> *mut std::os::raw::c_void +{ + let state = cast_pointer!(state,NTPState); + match state.get_tx_by_id(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ntp_state_get_tx_count(state: *mut std::os::raw::c_void) + -> u64 +{ + let state = cast_pointer!(state,NTPState); + state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ntp_state_tx_free(state: *mut std::os::raw::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state,NTPState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_ntp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, + _direction: u8) + -> std::os::raw::c_int +{ + 1 +} + +static mut ALPROTO_NTP : AppProto = ALPROTO_UNKNOWN; + +#[no_mangle] +pub extern "C" fn ntp_probing_parser(_flow: *const Flow, + _direction: u8, + input:*const u8, input_len: u32, + _rdir: *mut u8) -> AppProto +{ + let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, input_len as usize) }; + let alproto = unsafe{ ALPROTO_NTP }; + match parse_ntp(slice) { + Ok((_, _)) => { + // parse_ntp already checks for supported version (3 or 4) + return alproto; + }, + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + }, + Err(_) => { + return unsafe{ALPROTO_FAILED}; + }, + } +} + +export_tx_data_get!(rs_ntp_get_tx_data, NTPTransaction); +export_state_data_get!(rs_ntp_get_state_data, NTPState); + +const PARSER_NAME : &[u8] = b"ntp\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_register_ntp_parser() { + let default_port = CString::new("123").unwrap(); + let parser = RustParser { + name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port : default_port.as_ptr(), + ipproto : core::IPPROTO_UDP, + probe_ts : Some(ntp_probing_parser), + probe_tc : Some(ntp_probing_parser), + min_depth : 0, + max_depth : 16, + state_new : rs_ntp_state_new, + state_free : rs_ntp_state_free, + tx_free : rs_ntp_state_tx_free, + parse_ts : rs_ntp_parse_request, + parse_tc : rs_ntp_parse_response, + get_tx_count : rs_ntp_state_get_tx_count, + get_tx : rs_ntp_state_get_tx, + tx_comp_st_ts : 1, + tx_comp_st_tc : 1, + tx_get_progress : rs_ntp_tx_get_alstate_progress, + get_eventinfo : Some(NTPEvent::get_event_info), + get_eventinfo_byid : Some(NTPEvent::get_event_info_by_id), + localstorage_new : None, + localstorage_free : None, + get_tx_files : None, + get_tx_iterator : Some(applayer::state_get_tx_iterator::<NTPState, NTPTransaction>), + get_tx_data : rs_ntp_get_tx_data, + get_state_data : rs_ntp_get_state_data, + apply_tx_config : None, + flags : 0, + truncate : None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + // store the allocated ID for the probe function + ALPROTO_NTP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for NTP."); + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_ntp_parse_request_valid() { + // A UDP NTP v4 request, in client mode + const REQ : &[u8] = &[ + 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x57, 0xab, 0xc3, 0x4a, 0x5f, 0x2c, 0xfe + ]; + + let mut state = NTPState::new(); + assert_eq!(0, state.parse(REQ, Direction::ToServer)); + } +} diff --git a/rust/src/pgsql/logger.rs b/rust/src/pgsql/logger.rs new file mode 100644 index 0000000..934b549 --- /dev/null +++ b/rust/src/pgsql/logger.rs @@ -0,0 +1,300 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Juliana Fajardini <jufajardini@gmail.com> + +//! PostgreSQL parser json logger + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::pgsql::parser::*; +use crate::pgsql::pgsql::*; +use std; + +pub const PGSQL_LOG_PASSWORDS: u32 = BIT_U32!(0); + +fn log_pgsql(tx: &PgsqlTransaction, flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.set_uint("tx_id", tx.tx_id)?; + if let Some(request) = &tx.request { + js.set_object("request", &log_request(request, flags)?)?; + } else if tx.responses.is_empty() { + SCLogDebug!("Suricata created an empty PGSQL transaction"); + // TODO Log anomaly event instead? + js.set_bool("request", false)?; + js.set_bool("response", false)?; + return Ok(()); + } + + if !tx.responses.is_empty() { + js.set_object("response", &log_response_object(tx)?)?; + } + + Ok(()) +} + +fn log_request(req: &PgsqlFEMessage, flags: u32) -> Result<JsonBuilder, JsonError> { + let mut js = JsonBuilder::try_new_object()?; + match req { + PgsqlFEMessage::StartupMessage(StartupPacket { + length: _, + proto_major, + proto_minor, + params, + }) => { + let proto = format!("{}.{}", proto_major, proto_minor); + js.set_string("protocol_version", &proto)?; + js.set_object("startup_parameters", &log_startup_parameters(params)?)?; + } + PgsqlFEMessage::SSLRequest(_) => { + js.set_string("message", "SSL Request")?; + } + PgsqlFEMessage::SASLInitialResponse(SASLInitialResponsePacket { + identifier: _, + length: _, + auth_mechanism, + param_length: _, + sasl_param, + }) => { + js.set_string("sasl_authentication_mechanism", auth_mechanism.to_str())?; + js.set_string_from_bytes("sasl_param", sasl_param)?; + } + PgsqlFEMessage::PasswordMessage(RegularPacket { + identifier: _, + length: _, + payload, + }) => { + if flags & PGSQL_LOG_PASSWORDS != 0 { + js.set_string_from_bytes("password", payload)?; + } + } + PgsqlFEMessage::SASLResponse(RegularPacket { + identifier: _, + length: _, + payload, + }) => { + js.set_string_from_bytes("sasl_response", payload)?; + } + PgsqlFEMessage::SimpleQuery(RegularPacket { + identifier: _, + length: _, + payload, + }) => { + js.set_string_from_bytes(req.to_str(), payload)?; + } + PgsqlFEMessage::CancelRequest(CancelRequestMessage { + pid, + backend_key, + }) => { + js.set_string("message", "cancel_request")?; + js.set_uint("process_id", (*pid).into())?; + js.set_uint("secret_key", (*backend_key).into())?; + } + PgsqlFEMessage::Terminate(TerminationMessage { + identifier: _, + length: _, + }) => { + js.set_string("message", req.to_str())?; + } + PgsqlFEMessage::UnknownMessageType(RegularPacket { + identifier: _, + length: _, + payload: _, + }) => { + // We don't want to log these, for now. Cf redmine: #6576 + } + } + js.close()?; + Ok(js) +} + +fn log_response_object(tx: &PgsqlTransaction) -> Result<JsonBuilder, JsonError> { + let mut jb = JsonBuilder::try_new_object()?; + let mut array_open = false; + for response in &tx.responses { + if let PgsqlBEMessage::ParameterStatus(msg) = response { + if !array_open { + jb.open_array("parameter_status")?; + array_open = true; + } + jb.append_object(&log_pgsql_param(&msg.param)?)?; + } else { + if array_open { + jb.close()?; + array_open = false; + } + log_response(response, &mut jb)?; + } + } + jb.close()?; + Ok(jb) +} + +fn log_response(res: &PgsqlBEMessage, jb: &mut JsonBuilder) -> Result<(), JsonError> { + match res { + PgsqlBEMessage::SSLResponse(message) => { + if let SSLResponseMessage::SSLAccepted = message { + jb.set_bool("ssl_accepted", true)?; + } else { + jb.set_bool("ssl_accepted", false)?; + } + } + PgsqlBEMessage::NoticeResponse(ErrorNoticeMessage { + identifier: _, + length: _, + message_body, + }) + | PgsqlBEMessage::ErrorResponse(ErrorNoticeMessage { + identifier: _, + length: _, + message_body, + }) => { + log_error_notice_field_types(message_body, jb)?; + } + PgsqlBEMessage::AuthenticationMD5Password(AuthenticationMessage { + identifier: _, + length: _, + auth_type: _, + payload, + }) + | PgsqlBEMessage::AuthenticationSSPI(AuthenticationMessage { + identifier: _, + length: _, + auth_type: _, + payload, + }) + | PgsqlBEMessage::AuthenticationSASLFinal(AuthenticationMessage { + identifier: _, + length: _, + auth_type: _, + payload, + }) + | PgsqlBEMessage::CommandComplete(RegularPacket { + identifier: _, + length: _, + payload, + }) => { + jb.set_string_from_bytes(res.to_str(), payload)?; + } + PgsqlBEMessage::UnknownMessageType(RegularPacket { + identifier: _, + length: _, + payload: _, + }) => { + // We don't want to log these, for now. Cf redmine: #6576 + } + PgsqlBEMessage::AuthenticationOk(_) + | PgsqlBEMessage::AuthenticationCleartextPassword(_) + | PgsqlBEMessage::AuthenticationSASL(_) + | PgsqlBEMessage::AuthenticationSASLContinue(_) => { + jb.set_string("message", res.to_str())?; + } + PgsqlBEMessage::ParameterStatus(ParameterStatusMessage { + identifier: _, + length: _, + param: _, + }) => { + // We take care of these elsewhere + } + PgsqlBEMessage::BackendKeyData(BackendKeyDataMessage { + identifier: _, + length: _, + backend_pid, + secret_key, + }) => { + jb.set_uint("process_id", (*backend_pid).into())?; + jb.set_uint("secret_key", (*secret_key).into())?; + } + PgsqlBEMessage::ReadyForQuery(ReadyForQueryMessage { + identifier: _, + length: _, + transaction_status: _, + }) => { + // We don't want to log this one + } + PgsqlBEMessage::RowDescription(RowDescriptionMessage { + identifier: _, + length: _, + field_count, + fields: _, + }) => { + jb.set_uint("field_count", (*field_count).into())?; + } + PgsqlBEMessage::ConsolidatedDataRow(ConsolidatedDataRowPacket { + identifier: _, + row_cnt, + data_size, + }) => { + jb.set_uint("data_rows", *row_cnt)?; + jb.set_uint("data_size", *data_size)?; + } + PgsqlBEMessage::NotificationResponse(NotificationResponse { + identifier: _, + length: _, + pid, + channel_name, + payload, + }) => { + jb.set_uint("pid", (*pid).into())?; + jb.set_string_from_bytes("channel_name", channel_name)?; + jb.set_string_from_bytes("payload", payload)?; + } + } + Ok(()) +} + +fn log_error_notice_field_types( + error_fields: &Vec<PgsqlErrorNoticeMessageField>, jb: &mut JsonBuilder, +) -> Result<(), JsonError> { + for field in error_fields { + jb.set_string_from_bytes(field.field_type.to_str(), &field.field_value)?; + } + Ok(()) +} + +fn log_startup_parameters(params: &PgsqlStartupParameters) -> Result<JsonBuilder, JsonError> { + let mut jb = JsonBuilder::try_new_object()?; + // User is a mandatory field in a pgsql message + jb.set_string_from_bytes("user", ¶ms.user.value)?; + if let Some(parameters) = ¶ms.optional_params { + jb.open_array("optional_parameters")?; + for parameter in parameters { + jb.append_object(&log_pgsql_param(parameter)?)?; + } + jb.close()?; + } + + jb.close()?; + Ok(jb) +} + +fn log_pgsql_param(param: &PgsqlParameter) -> Result<JsonBuilder, JsonError> { + let mut jb = JsonBuilder::try_new_object()?; + jb.set_string_from_bytes(param.name.to_str(), ¶m.value)?; + jb.close()?; + Ok(jb) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_logger( + tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx_pgsql = cast_pointer!(tx, PgsqlTransaction); + SCLogDebug!( + "----------- PGSQL rs_pgsql_logger call. Tx id is {:?}", + tx_pgsql.tx_id + ); + log_pgsql(tx_pgsql, flags, js).is_ok() +} diff --git a/rust/src/pgsql/mod.rs b/rust/src/pgsql/mod.rs new file mode 100644 index 0000000..a4c79c0 --- /dev/null +++ b/rust/src/pgsql/mod.rs @@ -0,0 +1,24 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! PostgreSQL parser, logger and application layer module. +//! +//! written by Juliana Fajardini <jufajardini@oisf.net> + +pub mod logger; +pub mod parser; +pub mod pgsql; diff --git a/rust/src/pgsql/parser.rs b/rust/src/pgsql/parser.rs new file mode 100644 index 0000000..502d352 --- /dev/null +++ b/rust/src/pgsql/parser.rs @@ -0,0 +1,2338 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Juliana Fajardini <jufajardini@oisf.net> + +//! PostgreSQL nom parsers + +use crate::common::nom7::take_until_and_consume; +use nom7::branch::alt; +use nom7::bytes::streaming::{tag, take, take_until, take_until1}; +use nom7::character::streaming::{alphanumeric1, char}; +use nom7::combinator::{all_consuming, cond, eof, map_parser, opt, peek, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::{many1, many_m_n, many_till}; +use nom7::number::streaming::{be_i16, be_i32}; +use nom7::number::streaming::{be_u16, be_u32, be_u8}; +use nom7::sequence::{terminated, tuple}; +use nom7::{Err, IResult}; + +pub const PGSQL_LENGTH_FIELD: u32 = 4; + +pub const PGSQL_DUMMY_PROTO_MAJOR: u16 = 1234; // 0x04d2 +pub const PGSQL_DUMMY_PROTO_CANCEL_REQUEST: u16 = 5678; // 0x162e +pub const PGSQL_DUMMY_PROTO_MINOR_SSL: u16 = 5679; //0x162f +pub const _PGSQL_DUMMY_PROTO_MINOR_GSSAPI: u16 = 5680; // 0x1630 + +fn parse_length(i: &[u8]) -> IResult<&[u8], u32> { + verify(be_u32, |&x| x >= PGSQL_LENGTH_FIELD)(i) +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PgsqlParameters { + // startup parameters + User, + Database, + Options, + Replication, + // runtime parameters + ServerVersion, + ServerEncoding, + ClientEncoding, + ApplicationName, + DefaultTransactionReadOnly, + InHotStandby, + IsSuperuser, + SessionAuthorization, + DateStyle, + IntervalStyle, + TimeZone, + IntegerDatetimes, + StandardConformingStrings, + UnknownParameter(Vec<u8>), +} + +impl PgsqlParameters { + pub fn to_str(&self) -> &str { + match self { + PgsqlParameters::User => "user", + PgsqlParameters::Database => "database", + PgsqlParameters::Options => "options", + PgsqlParameters::Replication => "replication", + PgsqlParameters::ServerVersion => "server_version", + PgsqlParameters::ServerEncoding => "server_encoding", + PgsqlParameters::ClientEncoding => "client_encoding", + PgsqlParameters::ApplicationName => "application_name", + PgsqlParameters::DefaultTransactionReadOnly => "default_transaction_read_only", + PgsqlParameters::InHotStandby => "in_hot_standby", + PgsqlParameters::IsSuperuser => "is_superuser", + PgsqlParameters::SessionAuthorization => "session_authorization", + PgsqlParameters::DateStyle => "date_style", + PgsqlParameters::IntervalStyle => "interval_style", + PgsqlParameters::TimeZone => "time_zone", + PgsqlParameters::IntegerDatetimes => "integer_datetimes", + PgsqlParameters::StandardConformingStrings => "standard_conforming_strings", + PgsqlParameters::UnknownParameter(name) => { + std::str::from_utf8(name).unwrap_or("unknown_parameter") + } + } + } +} + +impl From<&[u8]> for PgsqlParameters { + fn from(name: &[u8]) -> Self { + match name { + br#"user"# => PgsqlParameters::User, + br#"database"# => PgsqlParameters::Database, + br#"options"# => PgsqlParameters::Options, + br#"replication"# => PgsqlParameters::Replication, + br#"server_version"# => PgsqlParameters::ServerVersion, + br#"server_encoding"# => PgsqlParameters::ServerEncoding, + br#"client_encoding"# => PgsqlParameters::ClientEncoding, + br#"application_name"# => PgsqlParameters::ApplicationName, + br#"default_transaction_read_only"# => PgsqlParameters::DefaultTransactionReadOnly, + br#"in_hot_standby"# => PgsqlParameters::InHotStandby, + br#"is_superuser"# => PgsqlParameters::IsSuperuser, + br#"session_authorization"# => PgsqlParameters::SessionAuthorization, + br#"DateStyle"# => PgsqlParameters::DateStyle, + br#"IntervalStyle"# => PgsqlParameters::IntervalStyle, + br#"TimeZone"# => PgsqlParameters::TimeZone, + br#"integer_datetimes"# => PgsqlParameters::IntegerDatetimes, + br#"standard_conforming_strings"# => PgsqlParameters::StandardConformingStrings, + _ => PgsqlParameters::UnknownParameter(name.to_vec()), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PgsqlParameter { + pub name: PgsqlParameters, + pub value: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PgsqlStartupParameters { + pub user: PgsqlParameter, + pub optional_params: Option<Vec<PgsqlParameter>>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct DummyStartupPacket { + length: u32, + proto_major: u16, + proto_minor: u16, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct StartupPacket { + pub length: u32, + pub proto_major: u16, + pub proto_minor: u16, + pub params: PgsqlStartupParameters, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RegularPacket { + pub identifier: u8, + pub length: u32, + pub payload: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PgsqlErrorNoticeMessageField { + pub field_type: PgsqlErrorNoticeFieldType, + pub field_value: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ErrorNoticeMessage { + pub identifier: u8, + pub length: u32, + pub message_body: Vec<PgsqlErrorNoticeMessageField>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum SSLResponseMessage { + SSLAccepted, + SSLRejected, + InvalidResponse, +} + +impl From<u8> for SSLResponseMessage { + fn from(identifier: u8) -> Self { + match identifier { + b'S' => Self::SSLAccepted, + b'N' => Self::SSLRejected, + _ => Self::InvalidResponse, + } + } +} + +impl From<char> for SSLResponseMessage { + fn from(identifier: char) -> Self { + match identifier { + 'S' => Self::SSLAccepted, + 'N' => Self::SSLRejected, + _ => Self::InvalidResponse, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ParameterStatusMessage { + pub identifier: u8, + pub length: u32, + pub param: PgsqlParameter, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct BackendKeyDataMessage { + pub identifier: u8, + pub length: u32, + pub backend_pid: u32, + pub secret_key: u32, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ConsolidatedDataRowPacket { + pub identifier: u8, + pub row_cnt: u64, + pub data_size: u64, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ReadyForQueryMessage { + pub identifier: u8, + pub length: u32, + pub transaction_status: u8, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct NotificationResponse { + pub identifier: u8, + pub length: u32, + pub pid: u32, + // two str fields, one right after the other + pub channel_name: Vec<u8>, + pub payload: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PgsqlBEMessage { + SSLResponse(SSLResponseMessage), + ErrorResponse(ErrorNoticeMessage), + NoticeResponse(ErrorNoticeMessage), + AuthenticationOk(AuthenticationMessage), + AuthenticationCleartextPassword(AuthenticationMessage), + AuthenticationMD5Password(AuthenticationMessage), + AuthenticationSSPI(AuthenticationMessage), + AuthenticationSASL(AuthenticationSASLMechanismMessage), + AuthenticationSASLContinue(AuthenticationMessage), + AuthenticationSASLFinal(AuthenticationMessage), + ParameterStatus(ParameterStatusMessage), + BackendKeyData(BackendKeyDataMessage), + CommandComplete(RegularPacket), + ReadyForQuery(ReadyForQueryMessage), + RowDescription(RowDescriptionMessage), + ConsolidatedDataRow(ConsolidatedDataRowPacket), + NotificationResponse(NotificationResponse), + UnknownMessageType(RegularPacket), +} + +impl PgsqlBEMessage { + pub fn to_str(&self) -> &'static str { + match self { + PgsqlBEMessage::SSLResponse(SSLResponseMessage::SSLAccepted) => "ssl_accepted", + PgsqlBEMessage::SSLResponse(SSLResponseMessage::SSLRejected) => "ssl_rejected", + PgsqlBEMessage::ErrorResponse(_) => "error_response", + PgsqlBEMessage::NoticeResponse(_) => "notice_response", + PgsqlBEMessage::AuthenticationOk(_) => "authentication_ok", + PgsqlBEMessage::AuthenticationCleartextPassword(_) => { + "authentication_cleartext_password" + } + PgsqlBEMessage::AuthenticationMD5Password(_) => "authentication_md5_password", + PgsqlBEMessage::AuthenticationSSPI(_) => "authentication_sspi", + PgsqlBEMessage::AuthenticationSASL(_) => "authentication_sasl", + PgsqlBEMessage::AuthenticationSASLContinue(_) => "authentication_sasl_continue", + PgsqlBEMessage::AuthenticationSASLFinal(_) => "authentication_sasl_final", + PgsqlBEMessage::ParameterStatus(_) => "parameter_status", + PgsqlBEMessage::BackendKeyData(_) => "backend_key_data", + PgsqlBEMessage::CommandComplete(_) => "command_completed", + PgsqlBEMessage::ReadyForQuery(_) => "ready_for_query", + PgsqlBEMessage::RowDescription(_) => "row_description", + PgsqlBEMessage::SSLResponse(SSLResponseMessage::InvalidResponse) => { + "invalid_be_message" + } + PgsqlBEMessage::ConsolidatedDataRow(_) => "data_row", + PgsqlBEMessage::NotificationResponse(_) => "notification_response", + PgsqlBEMessage::UnknownMessageType(_) => "unknown_message_type" + } + } + + pub fn get_backendkey_info(&self) -> (u32, u32) { + match self { + PgsqlBEMessage::BackendKeyData(message) => { + return (message.backend_pid, message.secret_key); + } + _ => (0, 0), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum SASLAuthenticationMechanism { + ScramSha256, + ScramSha256Plus, + // UnknownMechanism, +} + +impl SASLAuthenticationMechanism { + pub fn to_str(&self) -> &'static str { + match self { + SASLAuthenticationMechanism::ScramSha256 => "scram_SHA256", + SASLAuthenticationMechanism::ScramSha256Plus => "scram_SHA256_plus", + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct TerminationMessage { + pub identifier: u8, + pub length: u32, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct CancelRequestMessage { + pub pid: u32, + pub backend_key: u32, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PgsqlFEMessage { + SSLRequest(DummyStartupPacket), + StartupMessage(StartupPacket), + PasswordMessage(RegularPacket), + SASLInitialResponse(SASLInitialResponsePacket), + SASLResponse(RegularPacket), + SimpleQuery(RegularPacket), + CancelRequest(CancelRequestMessage), + Terminate(TerminationMessage), + UnknownMessageType(RegularPacket), +} + +impl PgsqlFEMessage { + pub fn to_str(&self) -> &'static str { + match self { + PgsqlFEMessage::StartupMessage(_) => "startup_message", + PgsqlFEMessage::SSLRequest(_) => "ssl_request", + PgsqlFEMessage::PasswordMessage(_) => "password_message", + PgsqlFEMessage::SASLInitialResponse(_) => "sasl_initial_response", + PgsqlFEMessage::SASLResponse(_) => "sasl_response", + PgsqlFEMessage::SimpleQuery(_) => "simple_query", + PgsqlFEMessage::CancelRequest(_) => "cancel_request", + PgsqlFEMessage::Terminate(_) => "termination_message", + PgsqlFEMessage::UnknownMessageType(_) => "unknown_message_type", + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AuthenticationMessage { + pub identifier: u8, + pub length: u32, + pub auth_type: u32, + pub payload: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SASLInitialResponsePacket { + pub identifier: u8, + pub length: u32, + pub auth_mechanism: SASLAuthenticationMechanism, + pub param_length: u32, + pub sasl_param: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct AuthenticationSASLMechanismMessage { + identifier: u8, + length: u32, + auth_type: u32, + auth_mechanisms: Vec<SASLAuthenticationMechanism>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RowField { + pub field_name: Vec<u8>, + pub table_oid: u32, + pub column_index: u16, + pub data_type_oid: u32, + // "see pg_type.typlen. Note that negative values denote variable-width types" + pub data_type_size: i16, + // "The value will generally be -1 for types that do not need pg_attribute.atttypmod." + pub type_modifier: i32, + // "The format code being used for the field. Currently will be zero (text) or one (binary). In a RowDescription returned from the variant of Describe, will always be zero" + pub format_code: u16, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RowDescriptionMessage { + pub identifier: u8, + pub length: u32, + pub field_count: u16, + pub fields: Vec<RowField>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ColumnFieldValue { + // Can be 0, or -1 as a special NULL column value + pub value_length: i32, + pub value: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum PgsqlErrorNoticeFieldType { + SeverityLocalizable, + SeverityNonLocalizable, + CodeSqlStateCode, + Message, + Detail, + Hint, + Position, + InternalPosition, + InternalQuery, + Where, + SchemaName, + TableName, + ColumnName, + DataType, + ConstraintName, + File, + Line, + Routine, + // Indicates end of message + TerminatorToken, + // From the documentation: "Since more field types might be added in future, frontends should silently ignore fields of unrecognized type." For us, then, I think the best option is actually to print it as we parse it, so it is readable? + UnknownFieldType, +} + +impl PgsqlErrorNoticeFieldType { + pub fn to_str(&self) -> &'static str { + match self { + PgsqlErrorNoticeFieldType::SeverityLocalizable => "severity_localizable", + PgsqlErrorNoticeFieldType::SeverityNonLocalizable => "severity_non_localizable", + PgsqlErrorNoticeFieldType::CodeSqlStateCode => "code", + PgsqlErrorNoticeFieldType::Message => "message", + PgsqlErrorNoticeFieldType::Detail => "detail", + PgsqlErrorNoticeFieldType::Hint => "hint", + PgsqlErrorNoticeFieldType::Position => "position", + PgsqlErrorNoticeFieldType::InternalPosition => "internal_position", + PgsqlErrorNoticeFieldType::InternalQuery => "internal_query", + PgsqlErrorNoticeFieldType::Where => "where", + PgsqlErrorNoticeFieldType::SchemaName => "schema_name", + PgsqlErrorNoticeFieldType::TableName => "table_name", + PgsqlErrorNoticeFieldType::ColumnName => "column_name", + PgsqlErrorNoticeFieldType::DataType => "data_type", + PgsqlErrorNoticeFieldType::ConstraintName => "constraint_name", + PgsqlErrorNoticeFieldType::File => "file", + PgsqlErrorNoticeFieldType::Line => "line", + PgsqlErrorNoticeFieldType::Routine => "routine", + PgsqlErrorNoticeFieldType::TerminatorToken => "", + PgsqlErrorNoticeFieldType::UnknownFieldType => "unknown_field_type", + } + } +} + +impl From<char> for PgsqlErrorNoticeFieldType { + fn from(identifier: char) -> PgsqlErrorNoticeFieldType { + match identifier { + 'S' => PgsqlErrorNoticeFieldType::SeverityLocalizable, + 'V' => PgsqlErrorNoticeFieldType::SeverityNonLocalizable, + 'C' => PgsqlErrorNoticeFieldType::CodeSqlStateCode, + 'M' => PgsqlErrorNoticeFieldType::Message, + 'D' => PgsqlErrorNoticeFieldType::Detail, + 'H' => PgsqlErrorNoticeFieldType::Hint, + 'P' => PgsqlErrorNoticeFieldType::Position, + 'p' => PgsqlErrorNoticeFieldType::InternalPosition, + 'q' => PgsqlErrorNoticeFieldType::InternalQuery, + 'W' => PgsqlErrorNoticeFieldType::Where, + 's' => PgsqlErrorNoticeFieldType::SchemaName, + 't' => PgsqlErrorNoticeFieldType::TableName, + 'c' => PgsqlErrorNoticeFieldType::ColumnName, + 'd' => PgsqlErrorNoticeFieldType::DataType, + 'n' => PgsqlErrorNoticeFieldType::ConstraintName, + 'F' => PgsqlErrorNoticeFieldType::File, + 'L' => PgsqlErrorNoticeFieldType::Line, + 'R' => PgsqlErrorNoticeFieldType::Routine, + '\u{0}' => PgsqlErrorNoticeFieldType::TerminatorToken, + // Pgsql documentation says "frontends should silently ignore fields of unrecognized type." + _ => PgsqlErrorNoticeFieldType::UnknownFieldType, + } + } +} + +impl From<u8> for PgsqlErrorNoticeFieldType { + fn from(identifier: u8) -> PgsqlErrorNoticeFieldType { + match identifier { + b'S' => PgsqlErrorNoticeFieldType::SeverityLocalizable, + b'V' => PgsqlErrorNoticeFieldType::SeverityNonLocalizable, + b'C' => PgsqlErrorNoticeFieldType::CodeSqlStateCode, + b'M' => PgsqlErrorNoticeFieldType::Message, + b'D' => PgsqlErrorNoticeFieldType::Detail, + b'H' => PgsqlErrorNoticeFieldType::Hint, + b'P' => PgsqlErrorNoticeFieldType::Position, + b'p' => PgsqlErrorNoticeFieldType::InternalPosition, + b'q' => PgsqlErrorNoticeFieldType::InternalQuery, + b'W' => PgsqlErrorNoticeFieldType::Where, + b's' => PgsqlErrorNoticeFieldType::SchemaName, + b't' => PgsqlErrorNoticeFieldType::TableName, + b'c' => PgsqlErrorNoticeFieldType::ColumnName, + b'd' => PgsqlErrorNoticeFieldType::DataType, + b'n' => PgsqlErrorNoticeFieldType::ConstraintName, + b'F' => PgsqlErrorNoticeFieldType::File, + b'L' => PgsqlErrorNoticeFieldType::Line, + b'R' => PgsqlErrorNoticeFieldType::Routine, + b'\0' => PgsqlErrorNoticeFieldType::TerminatorToken, + // Pgsql documentation says "frontends should silently ignore fields of unrecognized type." + _ => PgsqlErrorNoticeFieldType::UnknownFieldType, + } + } +} + +// Currently the set of parameters that could trigger a ParameterStatus message is fixed: +// server_version +// server_encoding +// client_encoding +// application_name +// default_transaction_read_only +// in_hot_standby +// is_superuser +// session_authorization +// DateStyle +// IntervalStyle +// TimeZone +// integer_datetimes +// standard_conforming_strings +// (source: PostgreSQL documentation) +// We may be interested, then, in controling this, somehow, to prevent weird things? +fn pgsql_parse_generic_parameter(i: &[u8]) -> IResult<&[u8], PgsqlParameter> { + let (i, param_name) = take_until1("\x00")(i)?; + let (i, _) = tag("\x00")(i)?; + let (i, param_value) = take_until("\x00")(i)?; + let (i, _) = tag("\x00")(i)?; + Ok((i, PgsqlParameter { + name: PgsqlParameters::from(param_name), + value: param_value.to_vec(), + })) +} + +pub fn pgsql_parse_startup_parameters(i: &[u8]) -> IResult<&[u8], PgsqlStartupParameters> { + let (i, mut optional) = opt(terminated(many1(pgsql_parse_generic_parameter), tag("\x00")))(i)?; + if let Some(ref mut params) = optional { + let mut user = PgsqlParameter{name: PgsqlParameters::User, value: Vec::new() }; + let mut index: usize = 0; + for (j, p) in params.iter().enumerate() { + if p.name == PgsqlParameters::User { + user.value.extend_from_slice(&p.value); + index = j; + } + } + params.remove(index); + if user.value.is_empty() { + return Err(Err::Error(make_error(i, ErrorKind::Tag))); + } + return Ok((i, PgsqlStartupParameters{ + user, + optional_params: if !params.is_empty() { + optional + } else { None }, + })); + } + return Err(Err::Error(make_error(i, ErrorKind::Tag))); +} + +fn parse_sasl_initial_response_payload(i: &[u8]) -> IResult<&[u8], (SASLAuthenticationMechanism, u32, Vec<u8>)> { + let (i, sasl_mechanism) = parse_sasl_mechanism(i)?; + let (i, param_length) = be_u32(i)?; + // From RFC 5802 - the client-first-message will always start w/ + // 'n', 'y' or 'p', otherwise it's invalid, I think we should check that, at some point + let (i, param) = terminated(take(param_length), eof)(i)?; + Ok((i, (sasl_mechanism, param_length, param.to_vec()))) +} + +pub fn parse_sasl_initial_response(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'p')(i)?; + let (i, length) = parse_length(i)?; + let (i, payload) = map_parser(take(length - PGSQL_LENGTH_FIELD), parse_sasl_initial_response_payload)(i)?; + Ok((i, PgsqlFEMessage::SASLInitialResponse( + SASLInitialResponsePacket { + identifier, + length, + auth_mechanism: payload.0, + param_length: payload.1, + sasl_param: payload.2, + }))) +} + +pub fn parse_sasl_response(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'p')(i)?; + let (i, length) = parse_length(i)?; + let (i, payload) = take(length - PGSQL_LENGTH_FIELD)(i)?; + let resp = PgsqlFEMessage::SASLResponse( + RegularPacket { + identifier, + length, + payload: payload.to_vec(), + }); + Ok((i, resp)) +} + +pub fn pgsql_parse_startup_packet(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, len) = verify(be_u32, |&x| x >= 8)(i)?; + let (i, proto_major) = peek(be_u16)(i)?; + let (i, b) = take(len - PGSQL_LENGTH_FIELD)(i)?; + let (_, message) = + match proto_major { + 1..=3 => { + let (b, proto_major) = be_u16(b)?; + let (b, proto_minor) = be_u16(b)?; + let (b, params) = pgsql_parse_startup_parameters(b)?; + (b, PgsqlFEMessage::StartupMessage(StartupPacket{ + length: len, + proto_major, + proto_minor, + params})) + }, + PGSQL_DUMMY_PROTO_MAJOR => { + let (b, proto_major) = be_u16(b)?; + let (b, proto_minor) = be_u16(b)?; + let (b, message) = match proto_minor { + PGSQL_DUMMY_PROTO_CANCEL_REQUEST => { + parse_cancel_request(b)? + }, + PGSQL_DUMMY_PROTO_MINOR_SSL => (b, PgsqlFEMessage::SSLRequest(DummyStartupPacket{ + length: len, + proto_major, + proto_minor + })), + _ => return Err(Err::Error(make_error(b, ErrorKind::Switch))), + }; + + (b, message) + } + _ => return Err(Err::Error(make_error(b, ErrorKind::Switch))), + }; + Ok((i, message)) +} + +// TODO Decide if it's a good idea to offer GSS encryption support right now, as the documentation seems to have conflicting information... +// If we do: +// To initiate a GSSAPI-encrypted connection, the frontend initially sends a GSSENCRequest message rather than a +// StartupMessage. The server then responds with a single byte containing G or N, indicating that it is willing or unwilling to perform GSSAPI encryption, respectively. The frontend might close the connection at this point if it is +// dissatisfied with the response. To continue after G, using the GSSAPI C bindings as discussed in RFC2744 or equivalent, +// perform a GSSAPI initialization by calling gss_init_sec_context() in a loop and sending the result to the server, +// starting with an empty input and then with each result from the server, until it returns no output. When sending the +// results of gss_init_sec_context() to the server, prepend the length of the message as a four byte integer in network +// byte order. To continue after N, send the usual StartupMessage and proceed without encryption. (Alternatively, it is +// permissible to issue an SSLRequest message after an N response to try to use SSL encryption instead of GSSAPI.) +// Source: https://www.postgresql.org/docs/13/protocol-flow.html#id-1.10.5.7.11, GSSAPI Session Encryption + +// Password can be encrypted or in cleartext +pub fn parse_password_message(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'p')(i)?; + let (i, length) = parse_length(i)?; + let (i, password) = map_parser( + take(length - PGSQL_LENGTH_FIELD), + take_until1("\x00") + )(i)?; + Ok((i, PgsqlFEMessage::PasswordMessage( + RegularPacket{ + identifier, + length, + payload: password.to_vec(), + }))) +} + +fn parse_simple_query(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'Q')(i)?; + let (i, length) = parse_length(i)?; + let (i, query) = map_parser(take(length - PGSQL_LENGTH_FIELD), take_until1("\x00"))(i)?; + Ok((i, PgsqlFEMessage::SimpleQuery(RegularPacket { + identifier, + length, + payload: query.to_vec(), + }))) +} + +fn parse_cancel_request(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, pid) = be_u32(i)?; + let (i, backend_key) = be_u32(i)?; + Ok((i, PgsqlFEMessage::CancelRequest(CancelRequestMessage { + pid, + backend_key, + }))) +} + +fn parse_terminate_message(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'X')(i)?; + let (i, length) = parse_length(i)?; + Ok((i, PgsqlFEMessage::Terminate(TerminationMessage { identifier, length }))) +} + +// Messages that begin with 'p' but are not password ones are not parsed here +pub fn parse_request(i: &[u8]) -> IResult<&[u8], PgsqlFEMessage> { + let (i, tag) = peek(be_u8)(i)?; + let (i, message) = match tag { + b'\0' => pgsql_parse_startup_packet(i)?, + b'Q' => parse_simple_query(i)?, + b'X' => parse_terminate_message(i)?, + _ => { + let (i, identifier) = be_u8(i)?; + let (i, length) = verify(be_u32, |&x| x > PGSQL_LENGTH_FIELD)(i)?; + let (i, payload) = take(length - PGSQL_LENGTH_FIELD)(i)?; + let unknown = PgsqlFEMessage::UnknownMessageType (RegularPacket{ + identifier, + length, + payload: payload.to_vec(), + }); + (i, unknown) + } + }; + Ok((i, message)) +} + +fn pgsql_parse_authentication_message<'a>(i: &'a [u8]) -> IResult<&'a [u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'R')(i)?; + let (i, length) = verify(be_u32, |&x| x >= 8)(i)?; + let (i, auth_type) = be_u32(i)?; + let (i, message) = map_parser( + take(length - 8), + |b: &'a [u8]| { + match auth_type { + 0 => Ok((b, PgsqlBEMessage::AuthenticationOk( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: b.to_vec(), + }))), + 3 => Ok((b, PgsqlBEMessage::AuthenticationCleartextPassword( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: b.to_vec(), + }))), + 5 => { + let (b, salt) = all_consuming(take(4_usize))(b)?; + Ok((b, PgsqlBEMessage::AuthenticationMD5Password( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: salt.to_vec(), + }))) + } + 9 => Ok((b, PgsqlBEMessage::AuthenticationSSPI( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: b.to_vec(), + }))), + // TODO - For SASL, should we parse specific details of the challenge itself? (as seen in: https://github.com/launchbadge/sqlx/blob/master/sqlx-core/src/postgres/message/authentication.rs ) + 10 => { + let (b, auth_mechanisms) = parse_sasl_mechanisms(b)?; + Ok((b, PgsqlBEMessage::AuthenticationSASL( + AuthenticationSASLMechanismMessage { + identifier, + length, + auth_type, + auth_mechanisms, + }))) + } + 11 => { + Ok((b, PgsqlBEMessage::AuthenticationSASLContinue( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: b.to_vec(), + }))) + }, + 12 => { + Ok((b, PgsqlBEMessage::AuthenticationSASLFinal( + AuthenticationMessage { + identifier, + length, + auth_type, + payload: b.to_vec(), + } + ))) + } + // TODO add other authentication messages + _ => return Err(Err::Error(make_error(i, ErrorKind::Switch))), + } + } + )(i)?; + Ok((i, message)) +} + +fn parse_parameter_status_message(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'S')(i)?; + let (i, length) = parse_length(i)?; + let (i, param) = map_parser(take(length - PGSQL_LENGTH_FIELD), pgsql_parse_generic_parameter)(i)?; + Ok((i, PgsqlBEMessage::ParameterStatus(ParameterStatusMessage { + identifier, + length, + param, + }))) +} + +pub fn parse_ssl_response(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, tag) = alt((char('N'), char('S')))(i)?; + Ok((i, PgsqlBEMessage::SSLResponse( + SSLResponseMessage::from(tag)) + )) +} + +fn parse_backend_key_data_message(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'K')(i)?; + let (i, length) = verify(be_u32, |&x| x == 12)(i)?; + let (i, pid) = be_u32(i)?; + let (i, secret_key) = be_u32(i)?; + Ok((i, PgsqlBEMessage::BackendKeyData(BackendKeyDataMessage { + identifier, + length, + backend_pid: pid, + secret_key, + }))) +} + +fn parse_command_complete(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'C')(i)?; + let (i, length) = parse_length(i)?; + let (i, payload) = map_parser(take(length - PGSQL_LENGTH_FIELD), take_until("\x00"))(i)?; + Ok((i, PgsqlBEMessage::CommandComplete(RegularPacket { + identifier, + length, + payload: payload.to_vec(), + }))) +} + +fn parse_ready_for_query(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'Z')(i)?; + let (i, length) = verify(be_u32, |&x| x == 5)(i)?; + let (i, status) = verify(be_u8, |&x| x == b'I' || x == b'T' || x == b'E')(i)?; + Ok((i, PgsqlBEMessage::ReadyForQuery(ReadyForQueryMessage { + identifier, + length, + transaction_status: status, + }))) +} + +fn parse_row_field(i: &[u8]) -> IResult<&[u8], RowField> { + let (i, field_name) = take_until1("\x00")(i)?; + let (i, _) = tag("\x00")(i)?; + let (i, table_oid) = be_u32(i)?; + let (i, column_index) = be_u16(i)?; + let (i, data_type_oid) = be_u32(i)?; + let (i, data_type_size) = be_i16(i)?; + let (i, type_modifier) = be_i32(i)?; + let (i, format_code) = be_u16(i)?; + Ok((i, RowField { + field_name: field_name.to_vec(), + table_oid, + column_index, + data_type_oid, + data_type_size, + type_modifier, + format_code, + })) +} + +pub fn parse_row_description(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'T')(i)?; + let (i, length) = verify(be_u32, |&x| x > 6)(i)?; + let (i, field_count) = be_u16(i)?; + let (i, fields) = map_parser( + take(length - 6), + many_m_n(0, field_count.into(), parse_row_field) + )(i)?; + Ok((i, PgsqlBEMessage::RowDescription( + RowDescriptionMessage { + identifier, + length, + field_count, + fields, + }))) +} + +fn parse_data_row_value(i: &[u8]) -> IResult<&[u8], ColumnFieldValue> { + let (i, value_length) = be_i32(i)?; + let (i, value) = cond(value_length >= 0, take(value_length as usize))(i)?; + Ok((i, ColumnFieldValue { + value_length, + value: { + match value { + Some(data) => data.to_vec(), + None => [].to_vec(), + } + }, + })) +} + +/// For each column, add up the data size. Return the total +fn add_up_data_size(columns: Vec<ColumnFieldValue>) -> u64 { + let mut data_size: u64 = 0; + for field in columns { + // -1 value means data value is NULL, let's not add that up + if field.value_length > 0 { + data_size += field.value_length as u64; + } + } + data_size +} + +// Currently, we don't store the actual DataRow messages, as those could easily become a burden, memory-wise +// We use ConsolidatedDataRow to store info we still want to log: message size. +// Later on, we calculate the number of lines the command actually returned by counting ConsolidatedDataRow messages +pub fn parse_consolidated_data_row(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'D')(i)?; + let (i, length) = verify(be_u32, |&x| x >= 6)(i)?; + let (i, field_count) = be_u16(i)?; + // 6 here is for skipping length + field_count + let (i, rows) = map_parser(take(length - 6), many_m_n(0, field_count.into(), parse_data_row_value))(i)?; + Ok((i, PgsqlBEMessage::ConsolidatedDataRow( + ConsolidatedDataRowPacket { + identifier, + row_cnt: 1, + data_size: add_up_data_size(rows), + } + ))) +} + +fn parse_sasl_mechanism(i: &[u8]) -> IResult<&[u8], SASLAuthenticationMechanism> { + let res: IResult<_, _, ()> = terminated(tag("SCRAM-SHA-256-PLUS"), tag("\x00"))(i); + if let Ok((i, _)) = res { + return Ok((i, SASLAuthenticationMechanism::ScramSha256Plus)); + } + let res: IResult<_, _, ()> = terminated(tag("SCRAM-SHA-256"), tag("\x00"))(i); + if let Ok((i, _)) = res { + return Ok((i, SASLAuthenticationMechanism::ScramSha256)); + } + return Err(Err::Error(make_error(i, ErrorKind::Alt))); +} + +fn parse_sasl_mechanisms(i: &[u8]) -> IResult<&[u8], Vec<SASLAuthenticationMechanism>> { + terminated(many1(parse_sasl_mechanism), tag("\x00"))(i) +} + +pub fn parse_error_response_code(i: &[u8]) -> IResult<&[u8], PgsqlErrorNoticeMessageField> { + let (i, _field_type) = char('C')(i)?; + let (i, field_value) = map_parser(take(6_usize), alphanumeric1)(i)?; + Ok((i, PgsqlErrorNoticeMessageField{ + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: field_value.to_vec(), + })) +} + +// Parse an error response with non-localizeable severity message. +// Possible values: ERROR, FATAL, or PANIC +pub fn parse_error_response_severity(i: &[u8]) -> IResult<&[u8], PgsqlErrorNoticeMessageField> { + let (i, field_type) = char('V')(i)?; + let (i, field_value) = alt((tag("ERROR"), tag("FATAL"), tag("PANIC")))(i)?; + let (i, _) = tag("\x00")(i)?; + Ok((i, PgsqlErrorNoticeMessageField{ + field_type: PgsqlErrorNoticeFieldType::from(field_type), + field_value: field_value.to_vec(), + })) +} + +// The non-localizable version of Severity field has different values, +// in case of a notice: 'WARNING', 'NOTICE', 'DEBUG', 'INFO' or 'LOG' +pub fn parse_notice_response_severity(i: &[u8]) -> IResult<&[u8], PgsqlErrorNoticeMessageField> { + let (i, field_type) = char('V')(i)?; + let (i, field_value) = alt(( + tag("WARNING"), + tag("NOTICE"), + tag("DEBUG"), + tag("INFO"), + tag("LOG")))(i)?; + let (i, _) = tag("\x00")(i)?; + Ok((i, PgsqlErrorNoticeMessageField{ + field_type: PgsqlErrorNoticeFieldType::from(field_type), + field_value: field_value.to_vec(), + })) +} + +pub fn parse_error_response_field( + i: &[u8], is_err_msg: bool, +) -> IResult<&[u8], PgsqlErrorNoticeMessageField> { + let (i, field_type) = peek(be_u8)(i)?; + let (i, data) = match field_type { + b'V' => { + if is_err_msg { + parse_error_response_severity(i)? + } else { + parse_notice_response_severity(i)? + } + } + b'C' => parse_error_response_code(i)?, + _ => { + let (i, field_type) = be_u8(i)?; + let (i, field_value) = take_until("\x00")(i)?; + let (i, _just_tag) = tag("\x00")(i)?; + let message = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::from(field_type), + field_value: field_value.to_vec(), + }; + return Ok((i, message)); + } + }; + Ok((i, data)) +} + +pub fn parse_error_notice_fields(i: &[u8], is_err_msg: bool) -> IResult<&[u8], Vec<PgsqlErrorNoticeMessageField>> { + let (i, data) = many_till(|b| parse_error_response_field(b, is_err_msg), tag("\x00"))(i)?; + Ok((i, data.0)) +} + +fn pgsql_parse_error_response(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'E')(i)?; + let (i, length) = verify(be_u32, |&x| x > 10)(i)?; + let (i, message_body) = map_parser( + take(length - PGSQL_LENGTH_FIELD), + |b| parse_error_notice_fields(b, true) + )(i)?; + + Ok((i, PgsqlBEMessage::ErrorResponse(ErrorNoticeMessage { + identifier, + length, + message_body, + }))) +} + +fn pgsql_parse_notice_response(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'N')(i)?; + let (i, length) = verify(be_u32, |&x| x > 10)(i)?; + let (i, message_body) = map_parser( + take(length - PGSQL_LENGTH_FIELD), + |b| parse_error_notice_fields(b, false) + )(i)?; + Ok((i, PgsqlBEMessage::NoticeResponse(ErrorNoticeMessage { + identifier, + length, + message_body, + }))) +} + +fn parse_notification_response(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, identifier) = verify(be_u8, |&x| x == b'A')(i)?; + // length (u32) + pid (u32) + at least one byte, for we have two str fields + let (i, length) = verify(be_u32, |&x| x > 9)(i)?; + let (i, data) = map_parser( + take(length - PGSQL_LENGTH_FIELD), + |b| { + let (b, pid) = be_u32(b)?; + let (b, channel_name) = take_until_and_consume(b"\x00")(b)?; + let (b, payload) = take_until_and_consume(b"\x00")(b)?; + Ok((b, (pid, channel_name, payload))) + })(i)?; + let msg = PgsqlBEMessage::NotificationResponse(NotificationResponse{ + identifier, + length, + pid: data.0, + channel_name: data.1.to_vec(), + payload: data.2.to_vec(), + }); + Ok((i, msg)) +} + +pub fn pgsql_parse_response(i: &[u8]) -> IResult<&[u8], PgsqlBEMessage> { + let (i, pseudo_header) = peek(tuple((be_u8, be_u32)))(i)?; + let (i, message) = + match pseudo_header.0 { + b'E' => pgsql_parse_error_response(i)?, + b'K' => parse_backend_key_data_message(i)?, + b'N' => pgsql_parse_notice_response(i)?, + b'R' => pgsql_parse_authentication_message(i)?, + b'S' => parse_parameter_status_message(i)?, + b'C' => parse_command_complete(i)?, + b'Z' => parse_ready_for_query(i)?, + b'T' => parse_row_description(i)?, + b'A' => parse_notification_response(i)?, + b'D' => parse_consolidated_data_row(i)?, + _ => { + let (i, identifier) = be_u8(i)?; + let (i, length) = verify(be_u32, |&x| x > PGSQL_LENGTH_FIELD)(i)?; + let (i, payload) = take(length - PGSQL_LENGTH_FIELD)(i)?; + let unknown = PgsqlBEMessage::UnknownMessageType (RegularPacket{ + identifier, + length, + payload: payload.to_vec(), + }); + (i, unknown) + } + + }; + Ok((i, message)) +} + +#[cfg(test)] +mod tests { + + use super::*; + use nom7::Needed; + + impl ErrorNoticeMessage { + pub fn new(identifier: u8, length: u32) -> Self { + ErrorNoticeMessage { + identifier, + length, + message_body: Vec::new(), + } + } + } + + #[test] + fn test_parse_request() { + // An SSLRequest + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + let ssl_request = DummyStartupPacket { + length: 8, + proto_major: PGSQL_DUMMY_PROTO_MAJOR, + proto_minor: PGSQL_DUMMY_PROTO_MINOR_SSL, + }; + let request_ok = PgsqlFEMessage::SSLRequest(ssl_request); + + let (_remainder, result) = parse_request(buf).unwrap(); + assert_eq!(result, request_ok); + + // incomplete message + let result = parse_request(&buf[0..7]); + assert!(result.is_err()); + + // Same request, but length is wrong + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x07, 0x04, 0xd2, 0x16, 0x2f]; + let result = parse_request(buf); + assert!(result.is_err()); + + let buf: &[u8] = &[ + /* Length 85 */ 0x00, 0x00, 0x00, 0x55, /* Proto version */ 0x00, 0x03, 0x00, + 0x00, /* user */ 0x75, 0x73, 0x65, 0x72, 0x00, /* [value] rep */ 0x72, 0x65, + 0x70, 0x00, /* database */ 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x00, + /* [optional] */ 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, + /* replication replication true application_name walreceiver */ + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x00, 0x74, 0x72, 0x75, 0x65, 0x00, 0x61, 0x70, 0x70, 0x6c, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x77, 0x61, + 0x6c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x00, 0x00, + ]; + let result = parse_request(buf); + match result { + Ok((remainder, _message)) => { + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + // A valid startup message/request without optional parameters + // ...&....user.oryx.database.mailstore.. + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x26, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x72, 0x00, 0x6f, + 0x72, 0x79, 0x78, 0x00, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x00, 0x6d, + 0x61, 0x69, 0x6c, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x00, 0x00, + ]; + let user = PgsqlParameter { + name: PgsqlParameters::User, + value: br#"oryx"#.to_vec(), + }; + let database = PgsqlParameter { + name: PgsqlParameters::Database, + value: br#"mailstore"#.to_vec(), + }; + let mut database_param: Vec<PgsqlParameter> = Vec::new(); + database_param.push(database); + let params = PgsqlStartupParameters { + user, + optional_params: Some(database_param), + }; + let expected_result = PgsqlFEMessage::StartupMessage(StartupPacket { + length: 38, + proto_major: 3, + proto_minor: 0, + params, + }); + let result = parse_request(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, expected_result); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + // A valid startup message/request without any optional parameters + // ........user.oryx.. + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x13, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x72, 0x00, 0x6f, + 0x72, 0x79, 0x78, 0x00, 0x00, + ]; + let user = PgsqlParameter { + name: PgsqlParameters::User, + value: br#"oryx"#.to_vec(), + }; + let params = PgsqlStartupParameters { + user, + optional_params: None, + }; + let expected_result = PgsqlFEMessage::StartupMessage(StartupPacket { + length: 19, + proto_major: 3, + proto_minor: 0, + params, + }); + let result = parse_request(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, expected_result); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + // A startup message/request with length off by one + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x12, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x72, 0x00, 0x6f, + 0x72, 0x79, 0x78, 0x00, 0x00, + ]; + let result = parse_request(buf); + assert!(result.is_err()); + + // A startup message/request with bad length + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x72, 0x00, 0x6f, + 0x72, 0x79, 0x78, 0x00, 0x00, + ]; + let result = parse_request(buf); + assert!(result.is_err()); + + // A startup message/request with corrupted user param + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x013, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x00, 0x6f, 0x72, + 0x79, 0x78, 0x00, 0x00, + ]; + let result = parse_request(buf); + assert!(result.is_err()); + + // A startup message/request missing the terminator + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x013, 0x00, 0x03, 0x00, 0x00, 0x75, 0x73, 0x65, 0x72, 0x00, 0x6f, + 0x72, 0x79, 0x78, 0x00, + ]; + let result = parse_request(buf); + assert!(result.is_err()); + + // A termination message + let buf: &[u8] = &[0x58, 0x00, 0x00, 0x00, 0x04]; + let result = parse_request(buf); + assert!(result.is_ok()); + + let result = parse_request(&buf[0..3]); + assert!(result.is_err()); + + } + + #[test] + fn test_cancel_request_message() { + // A cancel request message + let buf: &[u8] = &[ + 0x00, 0x00, 0x00, 0x10, // length: 16 (fixed) + 0x04, 0xd2, 0x16, 0x2e, // 1234.5678 - identifies a cancel request + 0x00, 0x00, 0x76, 0x31, // PID: 30257 + 0x23, 0x84, 0xf7, 0x2d]; // Backend key: 595916589 + let result = parse_cancel_request(buf); + assert!(result.is_ok()); + + let result = parse_cancel_request(&buf[0..3]); + assert!(result.is_err()); + + let result = pgsql_parse_startup_packet(buf); + assert!(result.is_ok()); + + let fail_result = pgsql_parse_startup_packet(&buf[0..3]); + assert!(fail_result.is_err()); + + let result = parse_request(buf); + assert!(result.is_ok()); + + let fail_result = parse_request(&buf[0..3]); + assert!(fail_result.is_err()); + } + + + + #[test] + fn test_parse_error_response_code() { + let buf: &[u8] = &[0x43, 0x32, 0x38, 0x30, 0x30, 0x30, 0x00]; + let value_str = "28000".as_bytes(); + let ok_res = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: value_str.to_vec(), + }; + let result = parse_error_response_code(buf); + assert!(result.is_ok()); + + let (remainder, result) = parse_error_response_code(buf).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + + let result = parse_error_response_code(&buf[0..5]); + assert!(result.is_err()); + } + + #[test] + fn test_parse_password_messages() { + // A password message (MD5) + let buf: &[u8] = &[ + 0x70, 0x00, 0x00, 0x00, 0x28, 0x6d, 0x64, 0x35, 0x63, 0x65, 0x66, 0x66, 0x63, 0x30, + 0x31, 0x64, 0x63, 0x64, 0x65, 0x37, 0x35, 0x34, 0x31, 0x38, 0x32, 0x39, 0x64, 0x65, + 0x65, 0x66, 0x36, 0x62, 0x35, 0x65, 0x39, 0x63, 0x39, 0x31, 0x34, 0x32, 0x00, + ]; + let ok_result = PgsqlFEMessage::PasswordMessage(RegularPacket { + identifier: b'p', + length: 40, + payload: br#"md5ceffc01dcde7541829deef6b5e9c9142"#.to_vec(), + }); + let (_remainder, result) = parse_password_message(buf).unwrap(); + assert_eq!(result, ok_result); + + // Length is off by one here + let buf: &[u8] = &[ + 0x70, 0x00, 0x00, 0x00, 0x27, 0x6d, 0x64, 0x35, 0x63, 0x65, 0x66, 0x66, 0x63, 0x30, + 0x31, 0x64, 0x63, 0x64, 0x65, 0x37, 0x35, 0x34, 0x31, 0x38, 0x32, 0x39, 0x64, 0x65, + 0x65, 0x66, 0x36, 0x62, 0x35, 0x65, 0x39, 0x63, 0x39, 0x31, 0x34, 0x32, 0x00, + ]; + let result = parse_password_message(buf); + assert!(result.is_err()); + + // Length also off by one, but now bigger than it should + let buf: &[u8] = &[ + 0x70, 0x00, 0x00, 0x00, 0x29, 0x6d, 0x64, 0x35, 0x63, 0x65, 0x66, 0x66, 0x63, 0x30, + 0x31, 0x64, 0x63, 0x64, 0x65, 0x37, 0x35, 0x34, 0x31, 0x38, 0x32, 0x39, 0x64, 0x65, + 0x65, 0x66, 0x36, 0x62, 0x35, 0x65, 0x39, 0x63, 0x39, 0x31, 0x34, 0x32, 0x00, + ]; + let result = parse_password_message(buf); + assert!(result.is_err()); + + // Incomplete payload + let buf: &[u8] = &[ + 0x70, 0x00, 0x00, 0x00, 0x28, 0x6d, 0x64, 0x35, 0x63, 0x65, 0x66, 0x66, 0x63, 0x30, + 0x31, 0x64, 0x63, 0x64, 0x65, 0x37, 0x35, 0x34, 0x31, 0x38, 0x32, 0x39, 0x64, 0x65, + 0x65, 0x66, 0x36, 0x62, 0x35, 0x65, 0x39, 0x63, 0x39, 0x31, 0x34, 0x32, + ]; + let result = parse_password_message(buf); + assert!(result.is_err()); + } + + #[test] + fn test_parse_error_response_field() { + // VFATAL + let input: &[u8] = &[0x56, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x00]; + + let value_str = "FATAL".as_bytes(); + let ok_res = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::SeverityNonLocalizable, + field_value: value_str.to_vec(), + }; + + let (remainder, result) = parse_error_response_field(input, true).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + + // "Mno pg_hba.conf entry for replication connection from host "192.168.50.11", user "rep", SSL off " + let input: &[u8] = &[ + 0x4d, 0x6e, 0x6f, 0x20, 0x70, 0x67, 0x5f, 0x68, 0x62, 0x61, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x68, 0x6f, + 0x73, 0x74, 0x20, 0x22, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x35, 0x30, + 0x2e, 0x31, 0x31, 0x22, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x22, 0x72, 0x65, + 0x70, 0x22, 0x2c, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x6f, 0x66, 0x66, 0x00, + ]; + + let value_str = br#"no pg_hba.conf entry for replication connection from host "192.168.50.11", user "rep", SSL off"#; + let ok_res = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Message, + field_value: value_str.to_vec(), + }; + + let (remainder, result) = parse_error_response_field(input, true).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + + // if incomplete, here we should get an error + let result = parse_error_response_field(&input[0..12], true); + assert!(result.is_err()); + + // C28000 + let input: &[u8] = &[0x43, 0x32, 0x38, 0x30, 0x30, 0x30, 0x00]; + let value_str = "28000".as_bytes(); + let ok_res = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: value_str.to_vec(), + }; + let (remainder, result) = parse_error_response_field(input, true).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + } + + // After sending AuthenticationOk, the backend will send a series of messages with parameters, a backend key message, and finally a ready for query message + #[test] + fn test_parse_startup_phase_wrapup() { + // S .application_name psql + let buf: &[u8] = &[ + 0x53, 0x00, 0x00, 0x00, 0x1a, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70, 0x73, 0x71, 0x6c, 0x00, + ]; + + let ok_res = PgsqlBEMessage::ParameterStatus(ParameterStatusMessage { + identifier: b'S', + length: 26, + param: PgsqlParameter { + name: PgsqlParameters::ApplicationName, + value: br#"psql"#.to_vec(), + }, + }); + + let (_remainder, result) = parse_parameter_status_message(buf).unwrap(); + assert_eq!(result, ok_res); + + let result = pgsql_parse_response(buf); + match result { + Ok((_remainder, message)) => { + assert_eq!(message, ok_res); + } + Err(Err::Error(err)) => { + panic!( + "Shouldn't be err {:?}, expected Ok(_). Remainder is: {:?} ", + err.code, err.input + ); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be incomplete {:?}, expected Ok(_)", needed); + } + _ => panic!("Unexpected behavior, expected Ok(_)"), + } + + // S .integer_datetimes on + let buf: &[u8] = &[ + 0x53, 0x00, 0x00, 0x00, 0x19, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x65, 0x72, 0x5f, 0x64, + 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x00, 0x6f, 0x6e, 0x00, + ]; + let result = parse_parameter_status_message(buf); + assert!(result.is_ok()); + + // K =.... // PID 61 Key 3152142766 + let buf: &[u8] = &[ + 0x4b, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x3d, 0xbb, 0xe1, 0xe1, 0xae, + ]; + + let result = parse_backend_key_data_message(buf); + assert!(result.is_ok()); + + // Z .I + let buf: &[u8] = &[0x5a, 0x00, 0x00, 0x00, 0x05, 0x49]; + let result = parse_ready_for_query(buf); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_error_notice_fields() { + let input: &[u8] = &[0x53, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x00, 0x00]; + + let field1 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::SeverityLocalizable, + field_value: br#"FATAL"#.to_vec(), + }; + let field2 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: br#"28000"#.to_vec(), + }; + let field3 = PgsqlErrorNoticeMessageField{ + field_type: PgsqlErrorNoticeFieldType::Message, + field_value: br#"no pg_hba.conf entry for replication connection from host "192.168.50.11", user "rep", SSL off"#.to_vec(), + }; + // let field4 = PgsqlErrorNoticeMessageField { + // field_type: PgsqlErrorNoticeFieldType::TerminatorToken, + // field_value: br#""#.to_vec(), + // }; + + let mut ok_res: Vec<PgsqlErrorNoticeMessageField> = Vec::new(); + ok_res.push(field1); + // ok_res.push(field4); + + let (remainder, result) = parse_error_notice_fields(input, true).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + // ok_res.pop(); + + ok_res.push(field2); + ok_res.push(field3); + + // let field4 = PgsqlErrorNoticeMessageField { + // field_type: PgsqlErrorNoticeFieldType::TerminatorToken, + // field_value: br#""#.to_vec(), + // }; + + // ok_res.push(field4); + + let input: &[u8] = &[ + 0x53, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x00, 0x43, 0x32, 0x38, 0x30, 0x30, 0x30, 0x00, + 0x4d, 0x6e, 0x6f, 0x20, 0x70, 0x67, 0x5f, 0x68, 0x62, 0x61, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x68, 0x6f, + 0x73, 0x74, 0x20, 0x22, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x35, 0x30, + 0x2e, 0x31, 0x31, 0x22, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x22, 0x72, 0x65, + 0x70, 0x22, 0x2c, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x6f, 0x66, 0x66, 0x00, 0x00, + ]; + + let (remainder, result) = parse_error_notice_fields(input, true).unwrap(); + assert_eq!(result, ok_res); + assert_eq!(remainder.len(), 0); + + let input: &[u8] = &[ + 0x53, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x00, 0x43, 0x32, 0x38, 0x30, 0x30, 0x30, 0x00, + 0x4d, 0x6e, 0x6f, 0x20, 0x70, 0x67, 0x5f, 0x68, 0x62, 0x61, 0x2e, 0x63, 0x6f, 0x6e, + 0x66, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x68, 0x6f, + 0x73, 0x74, 0x20, 0x22, 0x31, 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x35, 0x30, + 0x2e, 0x31, 0x31, 0x22, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x22, 0x72, 0x65, + 0x70, 0x22, 0x2c, 0x20, 0x53, 0x53, 0x4c, 0x20, 0x6f, 0x66, 0x66, 0x00, 0x46, 0x61, + 0x75, 0x74, 0x68, 0x2e, 0x63, 0x00, 0x4c, 0x34, 0x38, 0x31, 0x00, 0x52, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, + ]; + + let result = parse_error_notice_fields(input, true); + assert!(result.is_ok()); + + let result = parse_error_notice_fields(&input[0..12], true); + match result { + Ok((_remainder, _message)) => panic!("Result should not be ok, but incomplete."), + + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + assert_eq!(needed, Needed::new(2)); + } + _ => panic!("Unexpected behavior."), + } + } + + #[test] + fn test_parse_error_notice_response() { + // test case buffer + let buf: &[u8] = &[ + /* identifier */ 0x45, /* length */ 0x00, 0x00, 0x00, 0x96, + /* Severity */ 0x53, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x00, /* Code */ 0x43, + 0x32, 0x38, 0x30, 0x30, 0x30, 0x00, /* Message */ 0x4d, 0x6e, 0x6f, 0x20, 0x70, + 0x67, 0x5f, 0x68, 0x62, 0x61, 0x2e, 0x63, 0x6f, 0x6e, 0x66, 0x20, 0x65, 0x6e, 0x74, + 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x68, 0x6f, 0x73, 0x74, 0x20, 0x22, 0x31, + 0x39, 0x32, 0x2e, 0x31, 0x36, 0x38, 0x2e, 0x35, 0x30, 0x2e, 0x31, 0x31, 0x22, 0x2c, + 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x22, 0x72, 0x65, 0x70, 0x22, 0x2c, 0x20, 0x53, + 0x53, 0x4c, 0x20, 0x6f, 0x66, 0x66, 0x00, /* File */ 0x46, 0x61, 0x75, 0x74, 0x68, + 0x2e, 0x63, 0x00, /* Line */ 0x4c, 0x34, 0x38, 0x31, 0x00, + /* Routine */ 0x52, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x41, 0x75, 0x74, 0x68, + 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, + ]; + + // expected result + let field1 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::SeverityLocalizable, + field_value: br#"FATAL"#.to_vec(), + }; + let field2 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: br#"28000"#.to_vec(), + }; + let field3 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Message, + field_value: br#"no pg_hba.conf entry for replication connection from host "192.168.50.11", user "rep", SSL off"#.to_vec(), + }; + let field4 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::File, + field_value: br#"auth.c"#.to_vec(), + }; + let field5 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Line, + field_value: br#"481"#.to_vec(), + }; + let field6 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Routine, + field_value: br#"ClientAuthentication"#.to_vec(), + }; + + let mut payload = ErrorNoticeMessage::new(b'E', 150); + payload.message_body.push(field1); + payload.message_body.push(field2); + payload.message_body.push(field3); + payload.message_body.push(field4); + payload.message_body.push(field5); + payload.message_body.push(field6); + + let ok_res = PgsqlBEMessage::ErrorResponse(payload); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + let result_incomplete = pgsql_parse_response(&buf[0..22]); + match result_incomplete { + Err(Err::Incomplete(needed)) => { + // parser first tries to take whole message (length + identifier = 151), but buffer is incomplete + assert_eq!(needed, Needed::new(129)); + } + _ => { + panic!("Unexpected behavior. Should be incomplete."); + } + } + + //repeat for different case-scenarios: + // - message is valid + // some invalid character + } + + #[test] + fn test_parse_notice_response() { + // N .SDEBUG VDEBUG C23505 Mduplicate key value violates unique constraint "unique_a" DKey (a)=(mea5) already exists. Fnbtinsert.c L397 R_bt_check_unique + let buf: &[u8] = &[ + 0x4e, 0x00, 0x00, 0x00, 0x99, 0x53, 0x44, 0x45, 0x42, 0x55, 0x47, 0x00, 0x56, 0x44, + 0x45, 0x42, 0x55, 0x47, 0x00, 0x43, 0x32, 0x33, 0x35, 0x30, 0x35, 0x00, 0x4d, 0x64, + 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x20, 0x76, 0x69, 0x6f, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, + 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x74, 0x72, 0x61, + 0x69, 0x6e, 0x74, 0x20, 0x22, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x61, 0x22, + 0x00, 0x44, 0x4b, 0x65, 0x79, 0x20, 0x28, 0x61, 0x29, 0x3d, 0x28, 0x6d, 0x65, 0x61, + 0x35, 0x29, 0x20, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x20, 0x65, 0x78, 0x69, + 0x73, 0x74, 0x73, 0x2e, 0x00, 0x46, 0x6e, 0x62, 0x74, 0x69, 0x6e, 0x73, 0x65, 0x72, + 0x74, 0x2e, 0x63, 0x00, 0x4c, 0x33, 0x39, 0x37, 0x00, 0x52, 0x5f, 0x62, 0x74, 0x5f, + 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x00, 0x00, + ]; + + // expected result + let field1 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::SeverityLocalizable, + field_value: br#"DEBUG"#.to_vec(), + }; + let field2 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::SeverityNonLocalizable, + field_value: br#"DEBUG"#.to_vec(), + }; + let field3 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::CodeSqlStateCode, + field_value: br#"23505"#.to_vec(), + }; + let field4 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Message, + field_value: br#"duplicate key value violates unique constraint "unique_a""#.to_vec(), + }; + let field5 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Detail, + field_value: br#"Key (a)=(mea5) already exists."#.to_vec(), + }; + let field6 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::File, + field_value: br#"nbtinsert.c"#.to_vec(), + }; + let field7 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Line, + field_value: br#"397"#.to_vec(), + }; + let field8 = PgsqlErrorNoticeMessageField { + field_type: PgsqlErrorNoticeFieldType::Routine, + field_value: br#"_bt_check_unique"#.to_vec(), + }; + + let mut payload = ErrorNoticeMessage::new(b'N', 153); + payload.message_body.push(field1); + payload.message_body.push(field2); + payload.message_body.push(field3); + payload.message_body.push(field4); + payload.message_body.push(field5); + payload.message_body.push(field6); + payload.message_body.push(field7); + payload.message_body.push(field8); + + let ok_res = PgsqlBEMessage::NoticeResponse(payload); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?} ", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + } + + #[test] + fn test_parse_sasl_authentication_message() { + let buf: &[u8] = &[ + /* identifier R */ 0x52, /* length 28 */ 0x00, 0x00, 0x00, 0x1c, + /* auth_type */ 0x00, 0x00, 0x00, 0x0a, /* SCRAM-SHA-256-PLUS */ 0x53, 0x43, + 0x52, 0x41, 0x4d, 0x2d, 0x53, 0x48, 0x41, 0x2d, 0x32, 0x35, 0x36, 0x2d, 0x50, 0x4c, + 0x55, 0x53, 0x00, 0x00, + ]; + let mechanism = vec![SASLAuthenticationMechanism::ScramSha256Plus]; + let ok_res = PgsqlBEMessage::AuthenticationSASL(AuthenticationSASLMechanismMessage { + identifier: b'R', + length: 28, + auth_type: 10, + auth_mechanisms: mechanism, + }); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + let buf: &[u8] = &[ + /* identifier R */ 0x52, /* length 42 */ 0x00, 0x00, 0x00, 0x2a, + /* auth_type */ 0x00, 0x00, 0x00, 0x0a, /* SCRAM-SHA-256-PLUS */ 0x53, 0x43, + 0x52, 0x41, 0x4d, 0x2d, 0x53, 0x48, 0x41, 0x2d, 0x32, 0x35, 0x36, 0x2d, 0x50, 0x4c, + 0x55, 0x53, 0x00, /* SCRAM-SHA-256 */ 0x53, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x53, + 0x48, 0x41, 0x2d, 0x32, 0x35, 0x36, 0x00, 0x00, + ]; + let mechanism = vec![ + SASLAuthenticationMechanism::ScramSha256Plus, + SASLAuthenticationMechanism::ScramSha256, + ]; + let ok_res = PgsqlBEMessage::AuthenticationSASL(AuthenticationSASLMechanismMessage { + identifier: b'R', + length: 42, + auth_type: 10, + auth_mechanisms: mechanism, + }); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + let incomplete_result = pgsql_parse_response(&buf[0..27]); + match incomplete_result { + Ok((_remainder, _message)) => panic!("Should not be Ok(_), expected Incomplete!"), + Err(Err::Error(err)) => { + panic!("Should not be error {:?}, expected Incomplete!", err.code) + } + Err(Err::Incomplete(needed)) => assert_eq!(needed, Needed::new(16)), + _ => panic!("Unexpected behavior, expected Incomplete."), + } + } + + #[test] + fn test_parse_sasl_continue_authentication_message() { + // As found in: https://blog.hackeriet.no/Better-password-hashing-in-PostgreSQL/ + let buf: &[u8] = &[ + /* 'R' */ 0x52, /* 92 */ 0x00, 0x00, 0x00, 0x5c, /* 11 */ 0x00, 0x00, + 0x00, 0x0b, /* challenge data*/ 0x72, 0x3d, 0x2f, 0x7a, 0x2b, 0x67, 0x69, 0x5a, + 0x69, 0x54, 0x78, 0x41, 0x48, 0x37, 0x72, 0x38, 0x73, 0x4e, 0x41, 0x65, 0x48, 0x72, + 0x37, 0x63, 0x76, 0x70, 0x71, 0x56, 0x33, 0x75, 0x6f, 0x37, 0x47, 0x2f, 0x62, 0x4a, + 0x42, 0x49, 0x4a, 0x4f, 0x33, 0x70, 0x6a, 0x56, 0x4d, 0x37, 0x74, 0x33, 0x6e, 0x67, + 0x2c, 0x73, 0x3d, 0x34, 0x55, 0x56, 0x36, 0x38, 0x62, 0x49, 0x6b, 0x43, 0x38, 0x66, + 0x39, 0x2f, 0x58, 0x38, 0x78, 0x48, 0x37, 0x61, 0x50, 0x68, 0x67, 0x3d, 0x3d, 0x2c, + 0x69, 0x3d, 0x34, 0x30, 0x39, 0x36, + ]; + + let ok_res = PgsqlBEMessage::AuthenticationSASLContinue( + AuthenticationMessage { + identifier: b'R', + length: 92, + auth_type: 11, + payload: br#"r=/z+giZiTxAH7r8sNAeHr7cvpqV3uo7G/bJBIJO3pjVM7t3ng,s=4UV68bIkC8f9/X8xH7aPhg==,i=4096"#.to_vec(), + }); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error {:?} expected Ok(_)", err.code) + } + Err(Err::Incomplete(needed)) => { + panic!("shouldn't be incomplete {:?}, expected Ok(_)", needed) + } + _ => panic!("Unexpected behavior, expected Ok(_)"), + } + + let result_incomplete = pgsql_parse_response(&buf[0..31]); + match result_incomplete { + Ok((_remainder, _message)) => panic!("Should not be Ok(_), expected Incomplete!"), + Err(Err::Error(err)) => { + panic!("Shouldn't be error {:?} expected Incomplete!", err.code) + } + Err(Err::Incomplete(needed)) => { + assert_eq!(needed, Needed::new(62)); + } + _ => panic!("Unexpected behavior, expected Ok(_)"), + } + } + + #[test] + fn test_parse_sasl_final_authentication_message() { + let buf: &[u8] = &[ + /* R */ 0x52, /* 54 */ 0x00, 0x00, 0x00, 0x36, /* 12 */ 0x00, 0x00, + 0x00, 0x0c, /* signature */ 0x76, 0x3d, 0x64, 0x31, 0x50, 0x58, 0x61, 0x38, 0x54, + 0x4b, 0x46, 0x50, 0x5a, 0x72, 0x52, 0x33, 0x4d, 0x42, 0x52, 0x6a, 0x4c, 0x79, 0x33, + 0x2b, 0x4a, 0x36, 0x79, 0x78, 0x72, 0x66, 0x77, 0x2f, 0x7a, 0x7a, 0x70, 0x38, 0x59, + 0x54, 0x39, 0x65, 0x78, 0x56, 0x37, 0x73, 0x38, 0x3d, + ]; + let ok_res = PgsqlBEMessage::AuthenticationSASLFinal(AuthenticationMessage { + identifier: b'R', + length: 54, + auth_type: 12, + payload: br#"v=d1PXa8TKFPZrR3MBRjLy3+J6yxrfw/zzp8YT9exV7s8="#.to_vec(), + }); + + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error {:?}, expected Ok(_)", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Shouldn't be incomplete {:?}, expected OK(_)", needed); + } + _ => panic!("Unexpected behavior, expected Ok(_)"), + } + + let result_incomplete = pgsql_parse_response(&buf[0..34]); + match result_incomplete { + Err(Err::Incomplete(needed)) => { + assert_eq!(needed, Needed::new(21)); + } + _ => panic!("Unexpected behavior, expected incomplete."), + } + + let bad_buf: &[u8] = &[ + /* ` */ 0x60, /* 54 */ 0x00, 0x00, 0x00, 0x36, /* 12 */ 0x00, 0x00, + 0x00, 0x0c, /* signature */ 0x76, 0x3d, 0x64, 0x31, 0x50, 0x58, 0x61, 0x38, 0x54, + 0x4b, 0x46, 0x50, 0x5a, 0x72, 0x52, 0x33, 0x4d, 0x42, 0x52, 0x6a, 0x4c, 0x79, 0x33, + 0x2b, 0x4a, 0x36, 0x79, 0x78, 0x72, 0x66, 0x77, 0x2f, 0x7a, 0x7a, 0x70, 0x38, 0x59, + 0x54, 0x39, 0x65, 0x78, 0x56, 0x37, 0x73, 0x38, 0x3d, + ]; + let (remainder, result) = pgsql_parse_response(bad_buf).expect("parsing sasl final response failed"); + let res = PgsqlBEMessage::UnknownMessageType(RegularPacket { + identifier: b'`', + length: 54, + payload: bad_buf[5..].to_vec(), + }); + assert_eq!(result, res); + assert!(remainder.is_empty()); + } + + #[test] + fn test_parse_sasl_frontend_messages() { + // SASL Initial Response + // (as seen in https://blog.hackeriet.no/Better-password-hashing-in-PostgreSQL/) + let buf: &[u8] = &[ + /* p */ 0x70, /* 54 */ 0x00, 0x00, 0x00, 0x36, + /* sasl mechanism */ 0x53, 0x43, 0x52, 0x41, 0x4d, 0x2d, 0x53, 0x48, 0x41, 0x2d, + 0x32, 0x35, 0x36, 0x00, /* 32 */ 0x00, 0x00, 0x00, 0x20, + /* FE 1st msg */ 0x6e, 0x2c, 0x2c, 0x6e, 0x3d, 0x2c, 0x72, 0x3d, 0x2f, 0x7a, 0x2b, + 0x67, 0x69, 0x5a, 0x69, 0x54, 0x78, 0x41, 0x48, 0x37, 0x72, 0x38, 0x73, 0x4e, 0x41, + 0x65, 0x48, 0x72, 0x37, 0x63, 0x76, 0x70, + ]; + let ok_res = PgsqlFEMessage::SASLInitialResponse(SASLInitialResponsePacket { + identifier: b'p', + length: 54, + auth_mechanism: SASLAuthenticationMechanism::ScramSha256, + param_length: 32, + sasl_param: br#"n,,n=,r=/z+giZiTxAH7r8sNAeHr7cvp"#.to_vec(), + }); + + let result = parse_sasl_initial_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error {:?}, expected Ok(_)", err.code) + } + Err(Err::Incomplete(needed)) => { + panic!("Shouldn't be incomplete: {:?}, expected Ok(_)", needed) + } + _ => panic!("Unexpected behavior, expected Ok(_)"), + } + + let buf: &[u8] = &[ + /* p */ 0x70, /* 108 */ 0x00, 0x00, 0x00, 0x6c, /* final msg*/ 0x63, + 0x3d, 0x62, 0x69, 0x77, 0x73, 0x2c, 0x72, 0x3d, 0x2f, 0x7a, 0x2b, 0x67, 0x69, 0x5a, + 0x69, 0x54, 0x78, 0x41, 0x48, 0x37, 0x72, 0x38, 0x73, 0x4e, 0x41, 0x65, 0x48, 0x72, + 0x37, 0x63, 0x76, 0x70, 0x71, 0x56, 0x33, 0x75, 0x6f, 0x37, 0x47, 0x2f, 0x62, 0x4a, + 0x42, 0x49, 0x4a, 0x4f, 0x33, 0x70, 0x6a, 0x56, 0x4d, 0x37, 0x74, 0x33, 0x6e, 0x67, + 0x2c, 0x70, 0x3d, 0x41, 0x46, 0x70, 0x53, 0x59, 0x48, 0x2f, 0x4b, 0x2f, 0x38, 0x62, + 0x75, 0x78, 0x31, 0x6d, 0x52, 0x50, 0x55, 0x77, 0x78, 0x54, 0x65, 0x38, 0x6c, 0x42, + 0x75, 0x49, 0x50, 0x45, 0x79, 0x68, 0x69, 0x2f, 0x37, 0x55, 0x46, 0x50, 0x51, 0x70, + 0x53, 0x72, 0x34, 0x41, 0x3d, + ]; + + let ok_res = PgsqlFEMessage::SASLResponse( + RegularPacket { + identifier: b'p', + length: 108, + payload: br#"c=biws,r=/z+giZiTxAH7r8sNAeHr7cvpqV3uo7G/bJBIJO3pjVM7t3ng,p=AFpSYH/K/8bux1mRPUwxTe8lBuIPEyhi/7UFPQpSr4A="#.to_vec(), + }); + + let result = parse_sasl_response(buf); + match result { + Ok((_remainder, message)) => { + assert_eq!(message, ok_res); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?} expected Ok(_)", err.code) + } + Err(Err::Incomplete(needed)) => { + panic!("Shouldn't be incomplete: {:?}, expected Ok(_)", needed) + } + _ => panic!("Unexpected behavior, should be Ok(_)"), + } + } + + // Test messages with fixed formats, like AuthenticationSSPI + #[test] + fn test_parse_simple_authentication_requests() { + let buf: &[u8] = &[ + /* R */ 0x52, /* 8 */ 0x00, 0x00, 0x00, 0x08, /* 9 */ 0x00, 0x00, 0x00, + 0x09, + ]; + + let ok_res = PgsqlBEMessage::AuthenticationSSPI(AuthenticationMessage { + identifier: b'R', + length: 8, + auth_type: 9, + payload: Vec::<u8>::new(), + }); + + let (_remainder, result) = pgsql_parse_response(buf).unwrap(); + assert_eq!(result, ok_res); + } + + #[test] + fn test_parse_response() { + // An SSL response - N + let buf: &[u8] = &[0x4e]; + let response_ok = PgsqlBEMessage::SSLResponse(SSLResponseMessage::SSLRejected); + let (_remainder, result) = parse_ssl_response(buf).unwrap(); + assert_eq!(result, response_ok); + + // An SSL response - S + let buf: &[u8] = &[0x53]; + let response_ok = PgsqlBEMessage::SSLResponse(SSLResponseMessage::SSLAccepted); + + let (_remainder, result) = parse_ssl_response(buf).unwrap(); + assert_eq!(result, response_ok); + + // Not an SSL response + let buf: &[u8] = &[0x52]; + let result = parse_ssl_response(buf); + assert!(result.is_err()); + + // - auth MD5 + let buf: &[u8] = &[ + 0x52, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x11, 0xa3, 0xed, + ]; + let ok_res = PgsqlBEMessage::AuthenticationMD5Password(AuthenticationMessage { + identifier: b'R', + length: 12, + auth_type: 5, + payload: vec![0xf2, 0x11, 0xa3, 0xed], + }); + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + // - auth clear text... + let buf: &[u8] = &[0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03]; + let ok_res = PgsqlBEMessage::AuthenticationCleartextPassword(AuthenticationMessage { + identifier: b'R', + length: 8, + auth_type: 3, + payload: Vec::<u8>::new(), + }); + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(remainder.len(), 0); + assert_eq!(message, ok_res); + } + Err(Err::Error(err)) => { + panic!("Shouldn't be error: {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be incomplete. Needed {:?}", needed); + } + _ => { + panic!("Unexpected behavior"); + } + } + + let result = pgsql_parse_response(&buf[0..6]); + assert!(result.is_err()); + + let buf: &[u8] = &[0x52, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x03]; + let result = pgsql_parse_response(buf); + assert!(result.is_err()); + + // - auth Ok + let buf: &[u8] = &[0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00]; + let ok_res = PgsqlBEMessage::AuthenticationOk(AuthenticationMessage { + identifier: b'R', + length: 8, + auth_type: 0, + payload: Vec::<u8>::new(), + }); + let result = pgsql_parse_response(buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message, ok_res); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Should not be error {:?}", err.code); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be incomplete. Needed: {:?}", needed); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + //A series of response messages from the backend: + // R · S ·application_name + // S ·client_encoding UTF8 S ·DateStyle ISO, MDY + // S &default_transaction_read_only off S ·in_hot_standby off + // S ·integer_datetimes on S ·IntervalStyle postgres + // S ·is_superuser off S ·server_encoding UTF8 + // S ·server_version 14.5 S "session_authorization ctfpost + // S #standard_conforming_strings on S ·TimeZone Europe/Paris + // K ···O··Z ·I + let buf = &[ + 0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, + 0x00, 0x53, 0x00, 0x00, 0x00, 0x16, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x19, 0x63, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x5f, 0x65, 0x6e, 0x63, 0x6f, + 0x64, 0x69, 0x6e, 0x67, 0x00, 0x55, 0x54, 0x46, + 0x38, 0x00, 0x53, 0x00, 0x00, 0x00, 0x17, 0x44, + 0x61, 0x74, 0x65, 0x53, 0x74, 0x79, 0x6c, 0x65, + 0x00, 0x49, 0x53, 0x4f, 0x2c, 0x20, 0x4d, 0x44, + 0x59, 0x00, 0x53, 0x00, 0x00, 0x00, 0x26, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x74, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, + 0x6f, 0x6e, 0x6c, 0x79, 0x00, 0x6f, 0x66, 0x66, + 0x00, 0x53, 0x00, 0x00, 0x00, 0x17, 0x69, 0x6e, + 0x5f, 0x68, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, + 0x6e, 0x64, 0x62, 0x79, 0x00, 0x6f, 0x66, 0x66, + 0x00, 0x53, 0x00, 0x00, 0x00, 0x19, 0x69, 0x6e, + 0x74, 0x65, 0x67, 0x65, 0x72, 0x5f, 0x64, 0x61, + 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x00, + 0x6f, 0x6e, 0x00, 0x53, 0x00, 0x00, 0x00, 0x1b, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, + 0x53, 0x74, 0x79, 0x6c, 0x65, 0x00, 0x70, 0x6f, + 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x00, 0x53, + 0x00, 0x00, 0x00, 0x15, 0x69, 0x73, 0x5f, 0x73, + 0x75, 0x70, 0x65, 0x72, 0x75, 0x73, 0x65, 0x72, + 0x00, 0x6f, 0x66, 0x66, 0x00, 0x53, 0x00, 0x00, + 0x00, 0x19, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, + 0x67, 0x00, 0x55, 0x54, 0x46, 0x38, 0x00, 0x53, + 0x00, 0x00, 0x00, 0x18, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x00, 0x31, 0x34, 0x2e, 0x35, 0x00, + 0x53, 0x00, 0x00, 0x00, 0x22, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x00, 0x63, 0x74, 0x66, 0x70, 0x6f, + 0x73, 0x74, 0x00, 0x53, 0x00, 0x00, 0x00, 0x23, + 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, + 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, + 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x73, 0x00, 0x6f, 0x6e, 0x00, 0x53, + 0x00, 0x00, 0x00, 0x1a, 0x54, 0x69, 0x6d, 0x65, + 0x5a, 0x6f, 0x6e, 0x65, 0x00, 0x45, 0x75, 0x72, + 0x6f, 0x70, 0x65, 0x2f, 0x50, 0x61, 0x72, 0x69, + 0x73, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x0c, 0x00, + 0x00, 0x0b, 0x8d, 0xcf, 0x4f, 0xb6, 0xcf, 0x5a, + 0x00, 0x00, 0x00, 0x05, 0x49 + ]; + + let result = pgsql_parse_response(buf); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_row_description() { + // RowDescription message + // T .. + // source @ . ....... version @ . ....... sid @ . . ..... + let buffer: &[u8] = &[ + 0x54, 0x00, 0x00, 0x00, 0x50, 0x00, 0x03, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x00, + 0x00, 0x00, 0x40, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x19, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, + 0x40, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x73, 0x69, 0x64, 0x00, 0x00, 0x00, 0x40, 0x09, 0x00, 0x03, 0x00, 0x00, + 0x00, 0x14, 0x00, 0x08, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, + ]; + + let field1 = RowField { + field_name: br#"source"#.to_vec(), + table_oid: 16393, + column_index: 1, + data_type_oid: 25, + data_type_size: -1, + type_modifier: -1, + format_code: 0, + }; + + let field2 = RowField { + field_name: br#"version"#.to_vec(), + table_oid: 16393, + column_index: 2, + data_type_oid: 25, + data_type_size: -1, + type_modifier: -1, + format_code: 0, + }; + + let field3 = RowField { + field_name: br#"sid"#.to_vec(), + table_oid: 16393, + column_index: 3, + data_type_oid: 20, + data_type_size: 8, + type_modifier: -1, + format_code: 0, + }; + + let mut fields_vec = Vec::<RowField>::new(); + fields_vec.push(field1); + fields_vec.push(field2); + fields_vec.push(field3); + + let ok_res = PgsqlBEMessage::RowDescription(RowDescriptionMessage { + identifier: b'T', + length: 80, + field_count: 3, + fields: fields_vec, + }); + + let result = parse_row_description(buffer); + + match result { + Ok((rem, response)) => { + assert_eq!(response, ok_res); + assert!(rem.is_empty()); + } + Err(Err::Incomplete(needed)) => { + panic!("Should not be Incomplete! Needed: {:?}", needed); + } + Err(Err::Error(err)) => { + println!("Remainder is: {:?}", err.input); + panic!("Shouldn't be error: {:?}", err.code); + } + _ => { + panic!("Unexpected behavior"); + } + } + } + + #[test] + fn test_parse_data_row() { + let buffer: &[u8] = &[ + 0x44, 0x00, 0x00, 0x00, 0x23, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x65, 0x74, 0x2f, + 0x6f, 0x70, 0x65, 0x6e, 0x00, 0x00, 0x00, 0x03, 0x36, 0x2e, 0x30, 0x00, 0x00, 0x00, + 0x07, 0x32, 0x30, 0x32, 0x31, 0x37, 0x30, 0x31, + ]; + + let result = parse_consolidated_data_row(buffer); + assert!(result.is_ok()); + } + + #[test] + fn test_command_complete() { + let buffer: &[u8] = &[ + 0x43, 0x00, 0x00, 0x00, 0x0d, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x20, 0x33, 0x00, + ]; + + let ok_res = PgsqlBEMessage::CommandComplete(RegularPacket { + identifier: b'C', + length: 13, + payload: b"SELECT 3".to_vec(), + }); + + let result = pgsql_parse_response(buffer); + + match result { + Ok((rem, message)) => { + assert_eq!(ok_res, message); + assert!(rem.is_empty()); + } + Err(Err::Incomplete(needed)) => { + panic!( + "Shouldn't be Incomplete! Expected Ok(). Needed: {:?}", + needed + ); + } + Err(Err::Error(err)) => { + println!("Unparsed slice: {:?}", err.input); + panic!("Shouldn't be Error: {:?}, expected Ok()", err.code); + } + _ => { + panic!("Unexpected behavior, should be Ok()"); + } + } + } + + #[test] + fn test_parse_notification_response() { + // Handcrafted notification response message, based on documentation specification + // identifier: 'A' + // length: 39 + // pid: 61 + // channel_name: test_channel + // payload: Test notification + let buf: &[u8] = &[ + 0x41, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x3d, // test_channel + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x00, + // Test notification + 0x54, 0x65, 0x73, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x00, + ]; + + let ok_res = PgsqlBEMessage::NotificationResponse(NotificationResponse { + identifier: b'A', + length: 39, + pid: 61, + channel_name: br#"test_channel"#.to_vec(), + payload: br#"Test notification"#.to_vec(), + }); + + let (_rem, result) = pgsql_parse_response(buf).unwrap(); + + assert_eq!(ok_res, result); + } +} diff --git a/rust/src/pgsql/pgsql.rs b/rust/src/pgsql/pgsql.rs new file mode 100644 index 0000000..5c46008 --- /dev/null +++ b/rust/src/pgsql/pgsql.rs @@ -0,0 +1,903 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Juliana Fajardini <jufajardini@oisf.net> + +//! PostgreSQL parser + +use super::parser::{self, ConsolidatedDataRowPacket, PgsqlBEMessage, PgsqlFEMessage}; +use crate::applayer::*; +use crate::conf::*; +use crate::core::{AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use nom7::{Err, IResult}; +use std; +use std::collections::VecDeque; +use std::ffi::CString; + +pub const PGSQL_CONFIG_DEFAULT_STREAM_DEPTH: u32 = 0; + +static mut ALPROTO_PGSQL: AppProto = ALPROTO_UNKNOWN; + +static mut PGSQL_MAX_TX: usize = 1024; + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Debug)] +pub enum PgsqlTransactionState { + Init = 0, + RequestReceived, + ResponseDone, + FlushedOut, +} + +#[derive(Debug)] +pub struct PgsqlTransaction { + pub tx_id: u64, + pub tx_state: PgsqlTransactionState, + pub request: Option<PgsqlFEMessage>, + pub responses: Vec<PgsqlBEMessage>, + + pub data_row_cnt: u64, + pub data_size: u64, + + tx_data: AppLayerTxData, +} + +impl Transaction for PgsqlTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +impl Default for PgsqlTransaction { + fn default() -> Self { + Self::new() + } +} + +impl PgsqlTransaction { + pub fn new() -> Self { + Self { + tx_id: 0, + tx_state: PgsqlTransactionState::Init, + request: None, + responses: Vec::<PgsqlBEMessage>::new(), + data_row_cnt: 0, + data_size: 0, + tx_data: AppLayerTxData::new(), + } + } + + pub fn incr_row_cnt(&mut self) { + self.data_row_cnt = self.data_row_cnt.saturating_add(1); + } + + pub fn get_row_cnt(&self) -> u64 { + self.data_row_cnt + } + + pub fn sum_data_size(&mut self, row_size: u64) { + self.data_size += row_size; + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PgsqlStateProgress { + IdleState, + SSLRequestReceived, + SSLRejectedReceived, + StartupMessageReceived, + SASLAuthenticationReceived, + SASLInitialResponseReceived, + // SSPIAuthenticationReceived, // TODO implement + SASLResponseReceived, + SASLAuthenticationContinueReceived, + SASLAuthenticationFinalReceived, + SimpleAuthenticationReceived, + PasswordMessageReceived, + AuthenticationOkReceived, + ParameterSetup, + BackendKeyReceived, + ReadyForQueryReceived, + SimpleQueryReceived, + RowDescriptionReceived, + DataRowReceived, + CommandCompletedReceived, + ErrorMessageReceived, + CancelRequestReceived, + ConnectionTerminated, + #[cfg(test)] + UnknownState, + Finished, +} + +#[derive(Debug)] +pub struct PgsqlState { + state_data: AppLayerStateData, + tx_id: u64, + transactions: VecDeque<PgsqlTransaction>, + request_gap: bool, + response_gap: bool, + backend_secret_key: u32, + backend_pid: u32, + state_progress: PgsqlStateProgress, + tx_index_completed: usize, +} + +impl State<PgsqlTransaction> for PgsqlState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&PgsqlTransaction> { + self.transactions.get(index) + } +} + +impl Default for PgsqlState { + fn default() -> Self { + Self::new() + } +} + +impl PgsqlState { + pub fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + tx_id: 0, + transactions: VecDeque::new(), + request_gap: false, + response_gap: false, + backend_secret_key: 0, + backend_pid: 0, + state_progress: PgsqlStateProgress::IdleState, + tx_index_completed: 0, + } + } + + // Free a transaction by ID. + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&PgsqlTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self) -> PgsqlTransaction { + let mut tx = PgsqlTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + SCLogDebug!("Creating new transaction. tx_id: {}", tx.tx_id); + if self.transactions.len() > unsafe { PGSQL_MAX_TX } + self.tx_index_completed { + // If there are too many open transactions, + // mark the earliest ones as completed, and take care + // to avoid quadratic complexity + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if tx_old.tx_state < PgsqlTransactionState::ResponseDone { + tx_old.tx_state = PgsqlTransactionState::FlushedOut; + //TODO set event + break; + } + } + self.tx_index_completed = index; + } + return tx; + } + + /// Find or create a new transaction + /// + /// If a new transaction is created, push that into state.transactions before returning &mut to last tx + /// If we can't find a transaction and we should not create one, we return None + /// The moment when this is called will may impact the logic of transaction tracking (e.g. when a tx is considered completed) + // TODO A future, improved version may be based on current message type and dir, too + fn find_or_create_tx(&mut self) -> Option<&mut PgsqlTransaction> { + // First, check if we should create a new tx (in case the other was completed or there's no tx yet) + if self.state_progress == PgsqlStateProgress::IdleState + || self.state_progress == PgsqlStateProgress::StartupMessageReceived + || self.state_progress == PgsqlStateProgress::PasswordMessageReceived + || self.state_progress == PgsqlStateProgress::SASLInitialResponseReceived + || self.state_progress == PgsqlStateProgress::SASLResponseReceived + || self.state_progress == PgsqlStateProgress::SimpleQueryReceived + || self.state_progress == PgsqlStateProgress::SSLRequestReceived + || self.state_progress == PgsqlStateProgress::ConnectionTerminated + || self.state_progress == PgsqlStateProgress::CancelRequestReceived + { + let tx = self.new_tx(); + self.transactions.push_back(tx); + } + // If we don't need a new transaction, just return the current one + SCLogDebug!("find_or_create state is {:?}", &self.state_progress); + return self.transactions.back_mut(); + } + + /// Process State progress to decide if PgsqlTransaction is finished + /// + /// As Pgsql transactions are bidirectional and may be comprised of several + /// responses, we must track State progress to decide on tx completion + fn is_tx_completed(&self) -> bool { + if let PgsqlStateProgress::ReadyForQueryReceived + | PgsqlStateProgress::SSLRejectedReceived + | PgsqlStateProgress::SimpleAuthenticationReceived + | PgsqlStateProgress::SASLAuthenticationReceived + | PgsqlStateProgress::SASLAuthenticationContinueReceived + | PgsqlStateProgress::SASLAuthenticationFinalReceived + | PgsqlStateProgress::ConnectionTerminated + | PgsqlStateProgress::Finished = self.state_progress + { + true + } else { + false + } + } + + /// Define PgsqlState progression, based on the request received + /// + /// As PostgreSQL transactions can have multiple messages, State progression + /// is what helps us keep track of the PgsqlTransactions - when one finished + /// when the other starts. + /// State isn't directly updated to avoid reference borrowing conflicts. + fn request_next_state(request: &PgsqlFEMessage) -> Option<PgsqlStateProgress> { + match request { + PgsqlFEMessage::SSLRequest(_) => Some(PgsqlStateProgress::SSLRequestReceived), + PgsqlFEMessage::StartupMessage(_) => Some(PgsqlStateProgress::StartupMessageReceived), + PgsqlFEMessage::PasswordMessage(_) => Some(PgsqlStateProgress::PasswordMessageReceived), + PgsqlFEMessage::SASLInitialResponse(_) => { + Some(PgsqlStateProgress::SASLInitialResponseReceived) + } + PgsqlFEMessage::SASLResponse(_) => Some(PgsqlStateProgress::SASLResponseReceived), + PgsqlFEMessage::SimpleQuery(_) => { + SCLogDebug!("Match: SimpleQuery"); + Some(PgsqlStateProgress::SimpleQueryReceived) + // TODO here we may want to save the command that was received, to compare that later on when we receive command completed? + + // Important to keep in mind that: "In simple Query mode, the format of retrieved values is always text, except when the given command is a FETCH from a cursor declared with the BINARY option. In that case, the retrieved values are in binary format. The format codes given in the RowDescription message tell which format is being used." (from pgsql official documentation) + } + PgsqlFEMessage::CancelRequest(_) => Some(PgsqlStateProgress::CancelRequestReceived), + PgsqlFEMessage::Terminate(_) => { + SCLogDebug!("Match: Terminate message"); + Some(PgsqlStateProgress::ConnectionTerminated) + } + PgsqlFEMessage::UnknownMessageType(_) => { + SCLogDebug!("Match: Unknown message type"); + // Not changing state when we don't know the message + None + } + } + } + + fn state_based_req_parsing( + state: PgsqlStateProgress, input: &[u8], + ) -> IResult<&[u8], parser::PgsqlFEMessage> { + match state { + PgsqlStateProgress::SASLAuthenticationReceived => { + parser::parse_sasl_initial_response(input) + } + PgsqlStateProgress::SASLInitialResponseReceived + | PgsqlStateProgress::SASLAuthenticationContinueReceived => { + parser::parse_sasl_response(input) + } + PgsqlStateProgress::SimpleAuthenticationReceived => { + parser::parse_password_message(input) + } + _ => parser::parse_request(input), + } + } + + fn parse_request(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + // If there was gap, check we can sync up again. + if self.request_gap { + if parser::parse_request(input).is_ok() { + // The parser now needs to decide what to do as we are not in sync. + // For now, we'll just try again next time. + SCLogDebug!("Suricata interprets there's a gap in the request"); + return AppLayerResult::ok(); + } + + // It looks like we're in sync with the message header + // clear gap state and keep parsing. + self.request_gap = false; + } + + let mut start = input; + while !start.is_empty() { + SCLogDebug!( + "In 'parse_request' State Progress is: {:?}", + &self.state_progress + ); + match PgsqlState::state_based_req_parsing(self.state_progress, start) { + Ok((rem, request)) => { + start = rem; + if let Some(state) = PgsqlState::request_next_state(&request) { + self.state_progress = state; + }; + let tx_completed = self.is_tx_completed(); + if let Some(tx) = self.find_or_create_tx() { + tx.request = Some(request); + if tx_completed { + tx.tx_state = PgsqlTransactionState::ResponseDone; + } + } else { + // If there isn't a new transaction, we'll consider Suri should move on + return AppLayerResult::ok(); + }; + } + Err(Err::Incomplete(_needed)) => { + let consumed = input.len() - start.len(); + let needed_estimation = start.len() + 1; + SCLogDebug!( + "Needed: {:?}, estimated needed: {:?}", + _needed, + needed_estimation + ); + return AppLayerResult::incomplete(consumed as u32, needed_estimation as u32); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + + // Input was fully consumed. + return AppLayerResult::ok(); + } + + /// When the state changes based on a specific response, there are other actions we may need to perform + /// + /// If there is data from the backend message that Suri should store separately in the State or + /// Transaction, that is also done here + fn response_process_next_state( + &mut self, response: &PgsqlBEMessage, f: *const Flow, + ) -> Option<PgsqlStateProgress> { + match response { + PgsqlBEMessage::SSLResponse(parser::SSLResponseMessage::SSLAccepted) => { + SCLogDebug!("SSL Request accepted"); + unsafe { + AppLayerRequestProtocolTLSUpgrade(f); + } + Some(PgsqlStateProgress::Finished) + } + PgsqlBEMessage::SSLResponse(parser::SSLResponseMessage::SSLRejected) => { + SCLogDebug!("SSL Request rejected"); + Some(PgsqlStateProgress::SSLRejectedReceived) + } + PgsqlBEMessage::AuthenticationSASL(_) => { + Some(PgsqlStateProgress::SASLAuthenticationReceived) + } + PgsqlBEMessage::AuthenticationSASLContinue(_) => { + Some(PgsqlStateProgress::SASLAuthenticationContinueReceived) + } + PgsqlBEMessage::AuthenticationSASLFinal(_) => { + Some(PgsqlStateProgress::SASLAuthenticationFinalReceived) + } + PgsqlBEMessage::AuthenticationOk(_) => { + Some(PgsqlStateProgress::AuthenticationOkReceived) + } + PgsqlBEMessage::ParameterStatus(_) => Some(PgsqlStateProgress::ParameterSetup), + PgsqlBEMessage::BackendKeyData(_) => { + let backend_info = response.get_backendkey_info(); + self.backend_pid = backend_info.0; + self.backend_secret_key = backend_info.1; + Some(PgsqlStateProgress::BackendKeyReceived) + } + PgsqlBEMessage::ReadyForQuery(_) => Some(PgsqlStateProgress::ReadyForQueryReceived), + // TODO should we store any Parameter Status in PgsqlState? + PgsqlBEMessage::AuthenticationMD5Password(_) + | PgsqlBEMessage::AuthenticationCleartextPassword(_) => { + Some(PgsqlStateProgress::SimpleAuthenticationReceived) + } + PgsqlBEMessage::RowDescription(_) => Some(PgsqlStateProgress::RowDescriptionReceived), + PgsqlBEMessage::ConsolidatedDataRow(msg) => { + // Increment tx.data_size here, since we know msg type, so that we can later on log that info + self.transactions.back_mut()?.sum_data_size(msg.data_size); + Some(PgsqlStateProgress::DataRowReceived) + } + PgsqlBEMessage::CommandComplete(_) => { + // TODO Do we want to compare the command that was stored when + // query was sent with what we received here? + Some(PgsqlStateProgress::CommandCompletedReceived) + } + PgsqlBEMessage::ErrorResponse(_) => Some(PgsqlStateProgress::ErrorMessageReceived), + _ => { + // We don't always have to change current state when we see a response... + None + } + } + } + + fn state_based_resp_parsing( + state: PgsqlStateProgress, input: &[u8], + ) -> IResult<&[u8], parser::PgsqlBEMessage> { + if state == PgsqlStateProgress::SSLRequestReceived { + parser::parse_ssl_response(input) + } else { + parser::pgsql_parse_response(input) + } + } + + fn parse_response(&mut self, input: &[u8], flow: *const Flow) -> AppLayerResult { + // We're not interested in empty responses. + if input.is_empty() { + return AppLayerResult::ok(); + } + + if self.response_gap { + if !probe_tc(input) { + // Out of sync, we'll just try again next time. + SCLogDebug!("Suricata interprets there's a gap in the response"); + return AppLayerResult::ok(); + } + + // It seems we're in sync with a message header, clear gap state and keep parsing. + self.response_gap = false; + } + + let mut start = input; + while !start.is_empty() { + match PgsqlState::state_based_resp_parsing(self.state_progress, start) { + Ok((rem, response)) => { + start = rem; + SCLogDebug!("Response is {:?}", &response); + if let Some(state) = self.response_process_next_state(&response, flow) { + self.state_progress = state; + }; + let tx_completed = self.is_tx_completed(); + let curr_state = self.state_progress; + if let Some(tx) = self.find_or_create_tx() { + if curr_state == PgsqlStateProgress::DataRowReceived { + tx.incr_row_cnt(); + } else if curr_state == PgsqlStateProgress::CommandCompletedReceived + && tx.get_row_cnt() > 0 + { + // let's summarize the info from the data_rows in one response + let dummy_resp = + PgsqlBEMessage::ConsolidatedDataRow(ConsolidatedDataRowPacket { + identifier: b'D', + row_cnt: tx.get_row_cnt(), + data_size: tx.data_size, // total byte count of all data_row messages combined + }); + tx.responses.push(dummy_resp); + tx.responses.push(response); + } else { + tx.responses.push(response); + if tx_completed { + tx.tx_state = PgsqlTransactionState::ResponseDone; + } + } + } else { + // If there isn't a new transaction, we'll consider Suri should move on + return AppLayerResult::ok(); + }; + } + Err(Err::Incomplete(_needed)) => { + let consumed = input.len() - start.len(); + let needed_estimation = start.len() + 1; + SCLogDebug!( + "Needed: {:?}, estimated needed: {:?}, start is {:?}", + _needed, + needed_estimation, + &start + ); + return AppLayerResult::incomplete(consumed as u32, needed_estimation as u32); + } + Err(_) => { + SCLogDebug!("Error while parsing PostgreSQL response"); + return AppLayerResult::err(); + } + } + } + + // All input was fully consumed. + return AppLayerResult::ok(); + } + + fn on_request_gap(&mut self, _size: u32) { + self.request_gap = true; + } + + fn on_response_gap(&mut self, _size: u32) { + self.response_gap = true; + } +} + +/// Probe for a valid PostgreSQL response +/// +/// Currently, for parser usage only. We have a bit more logic in the function +/// used by the engine. +/// PGSQL messages don't have a header per se, so we parse the slice for an ok() +fn probe_tc(input: &[u8]) -> bool { + if parser::pgsql_parse_response(input).is_ok() || parser::parse_ssl_response(input).is_ok() { + return true; + } + SCLogDebug!("probe_tc is false"); + false +} + +// C exports. + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_probing_parser_ts( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + if input_len >= 1 && !input.is_null() { + + let slice: &[u8] = build_slice!(input, input_len as usize); + + match parser::parse_request(slice) { + Ok((_, request)) => { + if let PgsqlFEMessage::UnknownMessageType(_) = request { + return ALPROTO_FAILED; + } + return ALPROTO_PGSQL; + } + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + } + Err(_e) => { + return ALPROTO_FAILED; + } + } + } + return ALPROTO_UNKNOWN; +} + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_probing_parser_tc( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + if input_len >= 1 && !input.is_null() { + + let slice: &[u8] = build_slice!(input, input_len as usize); + + if parser::parse_ssl_response(slice).is_ok() { + return ALPROTO_PGSQL; + } + + match parser::pgsql_parse_response(slice) { + Ok((_, response)) => { + if let PgsqlBEMessage::UnknownMessageType(_) = response { + return ALPROTO_FAILED; + } + return ALPROTO_PGSQL; + } + Err(Err::Incomplete(_)) => { + return ALPROTO_UNKNOWN; + } + Err(_e) => { + return ALPROTO_FAILED; + } + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = PgsqlState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(unsafe { Box::from_raw(state as *mut PgsqlState) }); +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state_safe: &mut PgsqlState; + unsafe { + state_safe = cast_pointer!(state, PgsqlState); + } + state_safe.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_parse_request( + _flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + if stream_slice.is_empty() { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 { + SCLogDebug!(" Suricata reached `eof`"); + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } + } + + + let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState); + + if stream_slice.is_gap() { + state_safe.on_request_gap(stream_slice.gap_size()); + } else if !stream_slice.is_empty() { + return state_safe.parse_request(stream_slice.as_slice()); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_parse_response( + flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let _eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; + + let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState); + + if stream_slice.is_gap() { + state_safe.on_response_gap(stream_slice.gap_size()); + } else if !stream_slice.is_empty() { + return state_safe.parse_response(stream_slice.as_slice(), flow); + } + AppLayerResult::ok() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state_safe: &mut PgsqlState = cast_pointer!(state, PgsqlState); + match state_safe.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state_safe: &mut PgsqlState; + unsafe { + state_safe = cast_pointer!(state, PgsqlState); + } + return state_safe.tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_tx_get_state(tx: *mut std::os::raw::c_void) -> PgsqlTransactionState { + let tx_safe: &mut PgsqlTransaction; + unsafe { + tx_safe = cast_pointer!(tx, PgsqlTransaction); + } + return tx_safe.tx_state; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + return rs_pgsql_tx_get_state(tx) as i32; +} + +export_tx_data_get!(rs_pgsql_get_tx_data, PgsqlTransaction); +export_state_data_get!(rs_pgsql_get_state_data, PgsqlState); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"pgsql\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_register_parser() { + let default_port = CString::new("[5432]").unwrap(); + let mut stream_depth = PGSQL_CONFIG_DEFAULT_STREAM_DEPTH; + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_pgsql_probing_parser_ts), + probe_tc: Some(rs_pgsql_probing_parser_tc), + min_depth: 0, + max_depth: 16, + state_new: rs_pgsql_state_new, + state_free: rs_pgsql_state_free, + tx_free: rs_pgsql_state_tx_free, + parse_ts: rs_pgsql_parse_request, + parse_tc: rs_pgsql_parse_response, + get_tx_count: rs_pgsql_state_get_tx_count, + get_tx: rs_pgsql_state_get_tx, + tx_comp_st_ts: PgsqlTransactionState::RequestReceived as i32, + tx_comp_st_tc: PgsqlTransactionState::ResponseDone as i32, + tx_get_progress: rs_pgsql_tx_get_alstate_progress, + get_eventinfo: None, + get_eventinfo_byid: None, + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some( + crate::applayer::state_get_tx_iterator::<PgsqlState, PgsqlTransaction>, + ), + get_tx_data: rs_pgsql_get_tx_data, + get_state_data: rs_pgsql_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_PGSQL = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust pgsql parser registered."); + let retval = conf_get("app-layer.protocols.pgsql.stream-depth"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { + stream_depth = retval as u32; + } + Err(_) => { + SCLogError!("Invalid depth value"); + } + } + AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_PGSQL, stream_depth) + } + if let Some(val) = conf_get("app-layer.protocols.pgsql.max-tx") { + if let Ok(v) = val.parse::<usize>() { + PGSQL_MAX_TX = v; + } else { + SCLogError!("Invalid value for pgsql.max-tx"); + } + } + } else { + SCLogDebug!("Protocol detector and parser disabled for PGSQL."); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_response_probe() { + /* Authentication Request MD5 password salt value f211a3ed */ + let buf: &[u8] = &[ + 0x52, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x05, 0xf2, 0x11, 0xa3, 0xed, + ]; + assert!(probe_tc(buf)); + + /* R 8 -- Authentication Cleartext */ + let buf: &[u8] = &[0x52, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x03]; + assert!(probe_tc(buf)); + + let buf: &[u8] = &[ + /* R */ 0x52, /* 54 */ 0x00, 0x00, 0x00, 0x36, /* 12 */ 0x00, 0x00, + 0x00, 0x0c, /* signature */ 0x76, 0x3d, 0x64, 0x31, 0x50, 0x58, 0x61, 0x38, 0x54, + 0x4b, 0x46, 0x50, 0x5a, 0x72, 0x52, 0x33, 0x4d, 0x42, 0x52, 0x6a, 0x4c, 0x79, 0x33, + 0x2b, 0x4a, 0x36, 0x79, 0x78, 0x72, 0x66, 0x77, 0x2f, 0x7a, 0x7a, 0x70, 0x38, 0x59, + 0x54, 0x39, 0x65, 0x78, 0x56, 0x37, 0x73, 0x38, 0x3d, + ]; + assert!(probe_tc(buf)); + + /* S 26 -- parameter status application_name psql*/ + let buf: &[u8] = &[ + 0x53, 0x00, 0x00, 0x00, 0x1a, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x70, 0x73, 0x71, 0x6c, 0x00, + ]; + assert!(probe_tc(buf)); + } + + #[test] + fn test_request_events() { + let mut state = PgsqlState::new(); + // an SSL Request + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + state.parse_request(buf); + let ok_state = PgsqlStateProgress::SSLRequestReceived; + + assert_eq!(state.state_progress, ok_state); + + // TODO add test for startup request + } + + #[test] + fn test_incomplete_request() { + let mut state = PgsqlState::new(); + // An SSL Request + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + + let r = state.parse_request(&buf[0..0]); + assert_eq!( + r, + AppLayerResult { + status: 0, + consumed: 0, + needed: 0 + } + ); + + let r = state.parse_request(&buf[0..1]); + assert_eq!( + r, + AppLayerResult { + status: 1, + consumed: 0, + needed: 2 + } + ); + + let r = state.parse_request(&buf[0..2]); + assert_eq!( + r, + AppLayerResult { + status: 1, + consumed: 0, + needed: 3 + } + ); + } + + #[test] + fn test_find_or_create_tx() { + let mut state = PgsqlState::new(); + state.state_progress = PgsqlStateProgress::UnknownState; + let tx = state.find_or_create_tx(); + assert!(tx.is_none()); + + state.state_progress = PgsqlStateProgress::IdleState; + let tx = state.find_or_create_tx(); + assert!(tx.is_some()); + + // Now, even though there isn't a new transaction created, the previous one is available + state.state_progress = PgsqlStateProgress::SSLRejectedReceived; + let tx = state.find_or_create_tx(); + assert!(tx.is_some()); + assert_eq!(tx.unwrap().tx_id, 1); + } + + #[test] + fn test_row_cnt() { + let mut tx = PgsqlTransaction::new(); + assert_eq!(tx.get_row_cnt(), 0); + + tx.incr_row_cnt(); + assert_eq!(tx.get_row_cnt(), 1); + } +} diff --git a/rust/src/plugin.rs b/rust/src/plugin.rs new file mode 100644 index 0000000..ad214aa --- /dev/null +++ b/rust/src/plugin.rs @@ -0,0 +1,28 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Plugin utility module. + +pub fn init() { + unsafe { + let context = super::core::SCGetContext(); + super::core::init_ffi(context); + + let level = super::core::SCLogGetLogLevel(); + super::log::log_set_level(level); + } +} diff --git a/rust/src/quic/crypto.rs b/rust/src/quic/crypto.rs new file mode 100644 index 0000000..d40e845 --- /dev/null +++ b/rust/src/quic/crypto.rs @@ -0,0 +1,200 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use aes::cipher::generic_array::GenericArray; +use aes::Aes128; +use aes::BlockEncrypt; +use aes::NewBlockCipher; +use aes_gcm::AeadInPlace; +use aes_gcm::Aes128Gcm; +use aes_gcm::NewAead; +use hkdf::Hkdf; +use sha2::Sha256; + +pub const AES128_KEY_LEN: usize = 16; +pub const AES128_TAG_LEN: usize = 16; +pub const AES128_IV_LEN: usize = 12; + +pub struct HeaderProtectionKey(Aes128); + +impl HeaderProtectionKey { + fn new(secret: &[u8], version: u32) -> Self { + let hk = Hkdf::<Sha256>::from_prk(secret).unwrap(); + let mut secret = [0u8; AES128_KEY_LEN]; + let quichp = if version == 0x6b3343cf { + b"quicv2 hp" as &[u8] + } else { + b"quic hp" as &[u8] + }; + hkdf_expand_label(&hk, quichp, &mut secret, AES128_KEY_LEN as u16); + return Self(Aes128::new(GenericArray::from_slice(&secret))); + } + + pub fn decrypt_in_place( + &self, sample: &[u8], first: &mut u8, packet_number: &mut [u8], + ) -> Result<(), ()> { + let mut mask = GenericArray::clone_from_slice(sample); + self.0.encrypt_block(&mut mask); + + let (first_mask, pn_mask) = mask.split_first().unwrap(); + + let bits = if (*first & 0x80) != 0 { + 0x0f // Long header: 4 bits masked + } else { + 0x1f // Short header: 5 bits masked + }; + + *first ^= first_mask & bits; + let pn_len = (*first & 0x03) as usize + 1; + + for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) { + *dst ^= m; + } + + Ok(()) + } +} + +pub struct PacketKey { + key: Aes128Gcm, + iv: [u8; AES128_IV_LEN], +} + +impl PacketKey { + fn new(secret: &[u8], version: u32) -> Self { + let hk = Hkdf::<Sha256>::from_prk(secret).unwrap(); + let mut secret = [0u8; AES128_KEY_LEN]; + let quickey = if version == 0x6b3343cf { + b"quicv2 key" as &[u8] + } else { + b"quic key" as &[u8] + }; + hkdf_expand_label(&hk, quickey, &mut secret, AES128_KEY_LEN as u16); + let key = Aes128Gcm::new(GenericArray::from_slice(&secret)); + + let mut r = PacketKey { + key, + iv: [0u8; AES128_IV_LEN], + }; + let quiciv = if version == 0x6b3343cf { + b"quicv2 iv" as &[u8] + } else { + b"quic iv" as &[u8] + }; + hkdf_expand_label(&hk, quiciv, &mut r.iv, AES128_IV_LEN as u16); + return r; + } + + pub fn decrypt_in_place<'a>( + &self, packet_number: u64, header: &[u8], payload: &'a mut [u8], + ) -> Result<&'a [u8], ()> { + if payload.len() < AES128_TAG_LEN { + return Err(()); + } + let mut nonce = [0; AES128_IV_LEN]; + nonce[4..].copy_from_slice(&packet_number.to_be_bytes()); + for (nonce, inp) in nonce.iter_mut().zip(self.iv.iter()) { + *nonce ^= inp; + } + let tag_pos = payload.len() - AES128_TAG_LEN; + let (buffer, tag) = payload.split_at_mut(tag_pos); + let taga = GenericArray::from_slice(tag); + self.key + .decrypt_in_place_detached(GenericArray::from_slice(&nonce), header, buffer, taga) + .map_err(|_| ())?; + Ok(&payload[..tag_pos]) + } +} + +pub struct DirectionalKeys { + pub header: HeaderProtectionKey, + pub packet: PacketKey, +} + +impl DirectionalKeys { + fn new(secret: &[u8], version: u32) -> Self { + Self { + header: HeaderProtectionKey::new(secret, version), + packet: PacketKey::new(secret, version), + } + } +} + +pub struct QuicKeys { + pub local: DirectionalKeys, + pub remote: DirectionalKeys, +} + +fn hkdf_expand_label(hk: &Hkdf<Sha256>, label: &[u8], okm: &mut [u8], olen: u16) { + const LABEL_PREFIX: &[u8] = b"tls13 "; + + let output_len = u16::to_be_bytes(olen); + let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); + let context_len = u8::to_be_bytes(0); + + let info = &[ + &output_len[..], + &label_len[..], + LABEL_PREFIX, + label, + &context_len[..], + ]; + + hk.expand_multi_info(info, okm).unwrap(); +} + +pub fn quic_keys_initial(version: u32, client_dst_connection_id: &[u8]) -> Option<QuicKeys> { + let salt = match version { + 0x51303530 => &[ + 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, 0x5C, 0xFC, 0xDB, 0xD3, + 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45, + ], + 0xff00_001d..=0xff00_0020 => &[ + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2 + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, + 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99, + ], + 0xfaceb002 | 0xff00_0017..=0xff00_001c => &[ + // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-23#section-5.2 + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, + 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, + ], + 0x0000_0001 | 0xff00_0021..=0xff00_0022 => &[ + // https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, + 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, + ], + 0x6b3343cf => &[ + // https://www.rfc-editor.org/rfc/rfc9369.html#section-3.3.1 + 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, + 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9, + ], + _ => { + return None; + } + }; + let hk = Hkdf::<Sha256>::new(Some(salt), client_dst_connection_id); + let mut client_secret = [0u8; 32]; + hkdf_expand_label(&hk, b"client in", &mut client_secret, 32); + let mut server_secret = [0u8; 32]; + hkdf_expand_label(&hk, b"server in", &mut server_secret, 32); + + return Some(QuicKeys { + local: DirectionalKeys::new(&server_secret, version), + remote: DirectionalKeys::new(&client_secret, version), + }); +} diff --git a/rust/src/quic/cyu.rs b/rust/src/quic/cyu.rs new file mode 100644 index 0000000..0264678 --- /dev/null +++ b/rust/src/quic/cyu.rs @@ -0,0 +1,195 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::{ + frames::Frame, + parser::{QuicHeader, QuicVersion}, +}; +use md5::{Digest, Md5}; + +#[derive(Debug, PartialEq, Eq)] +pub struct Cyu { + pub string: String, + pub hash: String, +} + +impl Cyu { + pub(crate) fn new(string: String, hash: String) -> Self { + Self { string, hash } + } + + pub(crate) fn generate(header: &QuicHeader, frames: &[Frame]) -> Vec<Cyu> { + let version = match header.version { + QuicVersion::Q043 => Some("43"), + QuicVersion::Q044 => Some("44"), + QuicVersion::Q045 => Some("44"), + QuicVersion::Q046 => Some("46"), + _ => { + SCLogDebug!( + "Cannot match QUIC version {:?} to CYU version", + header.version + ); + None + } + }; + + let mut cyu_hashes = Vec::new(); + + if let Some(version) = version { + for frame in frames { + if let Frame::Stream(stream) = frame { + if let Some(tags) = &stream.tags { + let tags = tags + .iter() + .map(|(tag, _value)| tag.to_string()) + .collect::<Vec<String>>() + .join("-"); + + let cyu_string = format!("{},{}", version, tags); + + let mut hasher = Md5::new(); + hasher.update(cyu_string.as_bytes()); + let hash = hasher.finalize(); + + let cyu_hash = format!("{:x}", hash); + + cyu_hashes.push(Cyu::new(cyu_string, cyu_hash)); + } + } + } + } + + cyu_hashes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::quic::frames::{Frame, Stream, StreamTag}; + use crate::quic::parser::{PublicFlags, QuicType}; + use test_case::test_case; + + macro_rules! mock_header_and_frames { + ($version:expr, $($variants:expr),+) => {{ + let header = QuicHeader::new( + PublicFlags::new(0x80), + QuicType::Initial, + $version, + vec![], + vec![], + ); + + let frames = vec![ + Frame::Stream(Stream { + fin: false, + stream_id: vec![], + offset: vec![], + tags: Some(vec![$(($variants, vec![])),*]) + }) + ]; + + (header, frames) + }}; + } + + // Salesforce tests here: + // https://engineering.salesforce.com/gquic-protocol-analysis-and-fingerprinting-in-zeek-a4178855d75f + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q046, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Uaid, + StreamTag::Scid, StreamTag::Tcid, + StreamTag::Pdmd, StreamTag::Smhl, + StreamTag::Icsl, StreamTag::Nonp, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Scls, StreamTag::Kexs, + StreamTag::Xlct, StreamTag::Csct, + StreamTag::Copt, StreamTag::Ccrt, + StreamTag::Irtt, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "46,PAD-SNI-STK-VER-CCS-NONC-AEAD-UAID-SCID-TCID-PDMD-SMHL-ICSL-NONP-PUBS-MIDS-SCLS-KEXS-XLCT-CSCT-COPT-CCRT-IRTT-CFCW-SFCW".to_string(), + hash: "a46560d4548108cf99308319b3b85346".to_string(), + }; "test cyu 1" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Ver, StreamTag::Ccs, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-VER-CCS-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "e030dea1f2eea44ac7db5fe4de792acd".to_string(), + }; "test cyu 2" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Mids, StreamTag::Cfcw, + StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-SCID-PDMD-ICSL-MIDS-CFCW-SFCW".to_string(), + hash: "0811fab28e41e8c8a33e220a15b964d9".to_string(), + }; "test cyu 3" + )] + #[test_case( + mock_header_and_frames!( + // version + QuicVersion::Q043, + // tags + StreamTag::Pad, StreamTag::Sni, + StreamTag::Stk, StreamTag::Ver, + StreamTag::Ccs, StreamTag::Nonc, + StreamTag::Aead, StreamTag::Scid, + StreamTag::Pdmd, StreamTag::Icsl, + StreamTag::Pubs, StreamTag::Mids, + StreamTag::Kexs, StreamTag::Xlct, + StreamTag::Cfcw, StreamTag::Sfcw + ), + Cyu { + string: "43,PAD-SNI-STK-VER-CCS-NONC-AEAD-SCID-PDMD-ICSL-PUBS-MIDS-KEXS-XLCT-CFCW-SFCW".to_string(), + hash: "d8b208b236d176c89407500dbefb04c2".to_string(), + }; "test cyu 4" + )] + fn test_cyu_generate(input: (QuicHeader, Vec<Frame>), expected: Cyu) { + let (header, frames) = input; + + let cyu = Cyu::generate(&header, &frames); + assert_eq!(1, cyu.len()); + assert_eq!(expected, cyu[0]); + } +} diff --git a/rust/src/quic/detect.rs b/rust/src/quic/detect.rs new file mode 100644 index 0000000..7e9019b --- /dev/null +++ b/rust/src/quic/detect.rs @@ -0,0 +1,121 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::quic::quic::QuicTransaction; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_ua( + tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ua) = &tx.ua { + *buffer = ua.as_ptr(); + *buffer_len = ua.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_sni( + tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(sni) = &tx.sni { + *buffer = sni.as_ptr(); + *buffer_len = sni.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_ja3( + tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ja3) = &tx.ja3 { + *buffer = ja3.as_ptr(); + *buffer_len = ja3.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_version( + tx: &QuicTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if tx.header.flags.is_long { + let s = &tx.header.version_buf; + *buffer = s.as_ptr(); + *buffer_len = s.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_cyu_hash( + tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if (i as usize) < tx.cyu.len() { + let cyu = &tx.cyu[i as usize]; + + let p = &cyu.hash; + + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + + 0 + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_cyu_string( + tx: &QuicTransaction, i: u32, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if (i as usize) < tx.cyu.len() { + let cyu = &tx.cyu[i as usize]; + + let p = &cyu.string; + + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + 1 + } else { + *buffer = ptr::null(); + *buffer_len = 0; + + 0 + } +} diff --git a/rust/src/quic/error.rs b/rust/src/quic/error.rs new file mode 100644 index 0000000..89ac1fb --- /dev/null +++ b/rust/src/quic/error.rs @@ -0,0 +1,65 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::error::{ErrorKind, ParseError}; +use std::error::Error; +use std::fmt; + +#[derive(Debug, PartialEq, Eq)] +pub enum QuicError { + StreamTagNoMatch(u32), + InvalidPacket, + Incomplete, + Unhandled, + NomError(ErrorKind), +} + +impl<I> ParseError<I> for QuicError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + QuicError::NomError(kind) + } + + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + QuicError::NomError(kind) + } +} + +impl fmt::Display for QuicError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + QuicError::StreamTagNoMatch(tag) => { + write!(f, "Could not match stream tag: 0x{:x}", tag) + } + QuicError::Incomplete => write!(f, "Incomplete data"), + QuicError::InvalidPacket => write!(f, "Invalid packet"), + QuicError::Unhandled => write!(f, "Unhandled packet"), + QuicError::NomError(e) => write!(f, "Internal parser error {:?}", e), + } + } +} + +impl Error for QuicError {} + +impl From<nom7::Err<QuicError>> for QuicError { + fn from(err: nom7::Err<QuicError>) -> Self { + match err { + nom7::Err::Incomplete(_) => QuicError::Incomplete, + nom7::Err::Error(e) => e, + nom7::Err::Failure(e) => e, + } + } +} diff --git a/rust/src/quic/frames.rs b/rust/src/quic/frames.rs new file mode 100644 index 0000000..e1fb7d0 --- /dev/null +++ b/rust/src/quic/frames.rs @@ -0,0 +1,521 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::error::QuicError; +use crate::quic::parser::quic_var_uint; +use nom7::bytes::complete::take; +use nom7::combinator::{all_consuming, complete}; +use nom7::multi::{count, many0}; +use nom7::number::complete::{be_u16, be_u32, be_u8, le_u16, le_u32}; +use nom7::sequence::pair; +use nom7::IResult; +use num::FromPrimitive; +use std::fmt; +use tls_parser::TlsMessage::Handshake; +use tls_parser::TlsMessageHandshake::{ClientHello, ServerHello}; +use tls_parser::{ + parse_tls_extensions, parse_tls_message_handshake, TlsCipherSuiteID, TlsExtension, + TlsExtensionType, TlsMessage, +}; + +/// Tuple of StreamTag and offset +type TagOffset = (StreamTag, u32); + +/// Tuple of StreamTag and value +type TagValue = (StreamTag, Vec<u8>); + +#[derive(Debug, PartialEq)] +pub(crate) struct Stream { + pub fin: bool, + pub stream_id: Vec<u8>, + pub offset: Vec<u8>, + pub tags: Option<Vec<TagValue>>, +} + +#[repr(u32)] +#[derive(Debug, PartialEq, Clone, Copy, FromPrimitive)] +pub(crate) enum StreamTag { + Aead = 0x41454144, + Ccrt = 0x43435254, + Ccs = 0x43435300, + Cetv = 0x43455456, + Cfcw = 0x43464357, + Chlo = 0x43484c4f, + Copt = 0x434f5054, + Csct = 0x43534354, + Ctim = 0x4354494d, + Icsl = 0x4943534c, + Irtt = 0x49525454, + Kexs = 0x4b455853, + Mids = 0x4d494453, + Mspc = 0x4d535043, + Nonc = 0x4e4f4e43, + Nonp = 0x4e4f4e50, + Pad = 0x50414400, + Pdmd = 0x50444d44, + Pubs = 0x50554253, + Scid = 0x53434944, + Scls = 0x53434c53, + Sfcw = 0x53464357, + Smhl = 0x534d484c, + Sni = 0x534e4900, + Sno = 0x534e4f00, + Stk = 0x53544b00, + Tcid = 0x54434944, + Uaid = 0x55414944, + Ver = 0x56455200, + Xlct = 0x584c4354, +} + +impl fmt::Display for StreamTag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + StreamTag::Aead => "AEAD", + StreamTag::Ccrt => "CCRT", + StreamTag::Ccs => "CCS", + StreamTag::Cetv => "CETV", + StreamTag::Cfcw => "CFCW", + StreamTag::Chlo => "CHLO", + StreamTag::Copt => "COPT", + StreamTag::Csct => "CSCT", + StreamTag::Ctim => "CTIM", + StreamTag::Icsl => "ICSL", + StreamTag::Irtt => "IRTT", + StreamTag::Kexs => "KEXS", + StreamTag::Mids => "MIDS", + StreamTag::Mspc => "MSPC", + StreamTag::Nonc => "NONC", + StreamTag::Nonp => "NONP", + StreamTag::Pad => "PAD", + StreamTag::Pdmd => "PDMD", + StreamTag::Pubs => "PUBS", + StreamTag::Scid => "SCID", + StreamTag::Scls => "SCLS", + StreamTag::Sfcw => "SFCW", + StreamTag::Smhl => "SMHL", + StreamTag::Sni => "SNI", + StreamTag::Sno => "SNO", + StreamTag::Stk => "STK", + StreamTag::Tcid => "TCID", + StreamTag::Uaid => "UAID", + StreamTag::Ver => "VER", + StreamTag::Xlct => "XLCT", + } + ) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) struct Ack { + pub largest_acknowledged: u64, + pub ack_delay: u64, + pub ack_range_count: u64, + pub first_ack_range: u64, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct Crypto { + pub ciphers: Vec<TlsCipherSuiteID>, + // We remap the Vec<TlsExtension> from tls_parser::parse_tls_extensions because of + // the lifetime of TlsExtension due to references to the slice used for parsing + pub extv: Vec<QuicTlsExtension>, + pub ja3: String, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct CryptoFrag { + pub offset: u64, + pub length: u64, + pub data: Vec<u8>, +} + +#[derive(Debug, PartialEq)] +pub(crate) enum Frame { + Padding, + Ping, + Ack(Ack), + // this is more than a crypto frame : it contains a fully parsed tls hello + Crypto(Crypto), + // this is a regular quic crypto frame : they can be reassembled + // in order to parse a tls hello + CryptoFrag(CryptoFrag), + Stream(Stream), + Unknown(Vec<u8>), +} + +fn parse_padding_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + // nom take_while: cannot infer type for type parameter `Error` declared on the function `take_while` + let mut offset = 0; + while offset < input.len() { + if input[offset] != 0 { + break; + } + offset += 1; + } + return Ok((&input[offset..], Frame::Padding)); +} + +fn parse_ack_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, largest_acknowledged) = quic_var_uint(input)?; + let (rest, ack_delay) = quic_var_uint(rest)?; + let (rest, ack_range_count) = quic_var_uint(rest)?; + let (mut rest, first_ack_range) = quic_var_uint(rest)?; + + for _ in 0..ack_range_count { + //RFC9000 section 19.3.1. ACK Ranges + let (rest1, _gap) = quic_var_uint(rest)?; + let (rest1, _ack_range_length) = quic_var_uint(rest1)?; + rest = rest1; + } + + Ok(( + rest, + Frame::Ack(Ack { + largest_acknowledged, + ack_delay, + ack_range_count, + first_ack_range, + }), + )) +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct QuicTlsExtension { + pub etype: TlsExtensionType, + pub values: Vec<Vec<u8>>, +} + +fn quic_tls_ja3_client_extends(ja3: &mut String, exts: Vec<TlsExtension>) { + ja3.push(','); + let mut dash = false; + for e in &exts { + if let TlsExtension::EllipticCurves(x) = e { + for ec in x { + if dash { + ja3.push('-'); + } else { + dash = true; + } + ja3.push_str(&ec.0.to_string()); + } + } + } + ja3.push(','); + dash = false; + for e in &exts { + if let TlsExtension::EcPointFormats(x) = e { + for ec in *x { + if dash { + ja3.push('-'); + } else { + dash = true; + } + ja3.push_str(&ec.to_string()); + } + } + } +} + +// get interesting stuff out of parsed tls extensions +fn quic_get_tls_extensions( + input: Option<&[u8]>, ja3: &mut String, client: bool, +) -> Vec<QuicTlsExtension> { + let mut extv = Vec::new(); + if let Some(extr) = input { + if let Ok((_, exts)) = parse_tls_extensions(extr) { + let mut dash = false; + for e in &exts { + let etype = TlsExtensionType::from(e); + if dash { + ja3.push('-'); + } else { + dash = true; + } + ja3.push_str(&u16::from(etype).to_string()); + let mut values = Vec::new(); + match e { + TlsExtension::SNI(x) => { + for sni in x { + let mut value = Vec::new(); + value.extend_from_slice(sni.1); + values.push(value); + } + } + TlsExtension::ALPN(x) => { + for alpn in x { + let mut value = Vec::new(); + value.extend_from_slice(alpn); + values.push(value); + } + } + _ => {} + } + extv.push(QuicTlsExtension { etype, values }) + } + if client { + quic_tls_ja3_client_extends(ja3, exts); + } + } + } + return extv; +} + +fn parse_quic_handshake(msg: TlsMessage) -> Option<Frame> { + if let Handshake(hs) = msg { + match hs { + ClientHello(ch) => { + let mut ja3 = String::with_capacity(256); + ja3.push_str(&u16::from(ch.version).to_string()); + ja3.push(','); + let mut dash = false; + for c in &ch.ciphers { + if dash { + ja3.push('-'); + } else { + dash = true; + } + ja3.push_str(&u16::from(*c).to_string()); + } + ja3.push(','); + let ciphers = ch.ciphers; + let extv = quic_get_tls_extensions(ch.ext, &mut ja3, true); + return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 })); + } + ServerHello(sh) => { + let mut ja3 = String::with_capacity(256); + ja3.push_str(&u16::from(sh.version).to_string()); + ja3.push(','); + ja3.push_str(&u16::from(sh.cipher).to_string()); + ja3.push(','); + let ciphers = vec![sh.cipher]; + let extv = quic_get_tls_extensions(sh.ext, &mut ja3, false); + return Some(Frame::Crypto(Crypto { ciphers, extv, ja3 })); + } + _ => {} + } + } + return None; +} + +fn parse_crypto_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, offset) = quic_var_uint(input)?; + let (rest, length) = quic_var_uint(rest)?; + let (rest, data) = take(length as usize)(rest)?; + + if offset > 0 { + return Ok(( + rest, + Frame::CryptoFrag(CryptoFrag { + offset, + length, + data: data.to_vec(), + }), + )); + } + // if we have offset 0, try quick path : parse directly + match parse_tls_message_handshake(data) { + Ok((_, msg)) => { + if let Some(c) = parse_quic_handshake(msg) { + return Ok((rest, c)); + } + } + Err(nom7::Err::Incomplete(_)) => { + // offset 0 but incomplete : save it as a fragment for later reassembly + return Ok(( + rest, + Frame::CryptoFrag(CryptoFrag { + offset, + length, + data: data.to_vec(), + }), + )); + } + _ => {} + } + return Err(nom7::Err::Error(QuicError::InvalidPacket)); +} + +fn parse_tag(input: &[u8]) -> IResult<&[u8], StreamTag, QuicError> { + let (rest, tag) = be_u32(input)?; + + let tag = StreamTag::from_u32(tag).ok_or(nom7::Err::Error(QuicError::StreamTagNoMatch(tag)))?; + + Ok((rest, tag)) +} + +fn parse_tag_and_offset(input: &[u8]) -> IResult<&[u8], TagOffset, QuicError> { + pair(parse_tag, le_u32)(input) +} + +fn parse_crypto_stream(input: &[u8]) -> IResult<&[u8], Vec<TagValue>, QuicError> { + // [message tag][number of tag entries: N][pad][[tag][end offset], ...N][value data] + let (rest, _message_tag) = parse_tag(input)?; + + let (rest, num_entries) = le_u16(rest)?; + let (rest, _padding) = take(2usize)(rest)?; + + let (rest, tags_offset) = count(complete(parse_tag_and_offset), num_entries.into())(rest)?; + + // Convert (Tag, Offset) to (Tag, Value) + let mut tags = Vec::new(); + let mut previous_offset = 0; + let mut rest = rest; + for (tag, offset) in tags_offset { + // offsets should be increasing + let value_len = offset + .checked_sub(previous_offset) + .ok_or(nom7::Err::Error(QuicError::InvalidPacket))?; + let (new_rest, value) = take(value_len)(rest)?; + + previous_offset = offset; + rest = new_rest; + + tags.push((tag, value.to_vec())) + } + + Ok((rest, tags)) +} + +fn parse_stream_frame(input: &[u8], frame_ty: u8) -> IResult<&[u8], Frame, QuicError> { + // 0b1_f_d_ooo_ss + let fin = frame_ty & 0x40 == 0x40; + let has_data_length = frame_ty & 0x20 == 0x20; + + let offset_hdr_length = { + let mut offset_length = (frame_ty & 0x1c) >> 2; + if offset_length != 0 { + offset_length += 1; + } + offset_length + }; + + let stream_id_hdr_length = usize::from((frame_ty & 0x03) + 1); + + let (rest, stream_id) = take(stream_id_hdr_length)(input)?; + let (rest, offset) = take(offset_hdr_length)(rest)?; + + let (rest, data_length) = if has_data_length { + let (rest, data_length) = be_u16(rest)?; + + (rest, usize::from(data_length)) + } else { + (rest, rest.len()) + }; + + let (rest, stream_data) = take(data_length)(rest)?; + + let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) { + Some(tags) + } else { + None + }; + + Ok(( + rest, + Frame::Stream(Stream { + fin, + stream_id: stream_id.to_vec(), + offset: offset.to_vec(), + tags, + }), + )) +} + +fn parse_crypto_stream_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, _offset) = quic_var_uint(input)?; + let (rest, data_length) = quic_var_uint(rest)?; + if data_length > u32::MAX as u64 { + return Err(nom7::Err::Error(QuicError::Unhandled)); + } + let (rest, stream_data) = take(data_length as u32)(rest)?; + + let tags = if let Ok((_, tags)) = all_consuming(parse_crypto_stream)(stream_data) { + Some(tags) + } else { + None + }; + + Ok(( + rest, + Frame::Stream(Stream { + fin: false, + stream_id: Vec::new(), + offset: Vec::new(), + tags, + }), + )) +} + +impl Frame { + fn decode_frame(input: &[u8]) -> IResult<&[u8], Frame, QuicError> { + let (rest, frame_ty) = be_u8(input)?; + + // Special frame types + let (rest, value) = if frame_ty & 0x80 == 0x80 { + // STREAM + parse_stream_frame(rest, frame_ty)? + } else { + match frame_ty { + 0x00 => parse_padding_frame(rest)?, + 0x01 => (rest, Frame::Ping), + 0x02 => parse_ack_frame(rest)?, + 0x06 => parse_crypto_frame(rest)?, + 0x08 => parse_crypto_stream_frame(rest)?, + _ => ([].as_ref(), Frame::Unknown(rest.to_vec())), + } + }; + + Ok((rest, value)) + } + + pub(crate) fn decode_frames(input: &[u8]) -> IResult<&[u8], Vec<Frame>, QuicError> { + let (rest, mut frames) = many0(complete(Frame::decode_frame))(input)?; + + // reassemble crypto fragments : first find total size + let mut crypto_max_size = 0; + let mut crypto_total_size = 0; + for f in &frames { + if let Frame::CryptoFrag(c) = f { + if crypto_max_size < c.offset + c.length { + crypto_max_size = c.offset + c.length; + } + crypto_total_size += c.length; + } + } + if crypto_max_size > 0 && crypto_total_size == crypto_max_size { + // we have some, and no gaps from offset 0 + let mut d = vec![0; crypto_max_size as usize]; + for f in &frames { + if let Frame::CryptoFrag(c) = f { + d[c.offset as usize..(c.offset + c.length) as usize] + .clone_from_slice(&c.data); + } + } + if let Ok((_, msg)) = parse_tls_message_handshake(&d) { + if let Some(c) = parse_quic_handshake(msg) { + // add a parsed crypto frame + frames.push(c); + } + } + } + + Ok((rest, frames)) + } +} diff --git a/rust/src/quic/logger.rs b/rust/src/quic/logger.rs new file mode 100644 index 0000000..e03ebdd --- /dev/null +++ b/rust/src/quic/logger.rs @@ -0,0 +1,157 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::parser::QuicType; +use super::quic::QuicTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use digest::Digest; +use digest::Update; +use md5::Md5; + +fn quic_tls_extension_name(e: u16) -> Option<String> { + match e { + 0 => Some("server_name".to_string()), + 1 => Some("max_fragment_length".to_string()), + 2 => Some("client_certificate_url".to_string()), + 3 => Some("trusted_ca_keys".to_string()), + 4 => Some("truncated_hmac".to_string()), + 5 => Some("status_request".to_string()), + 6 => Some("user_mapping".to_string()), + 7 => Some("client_authz".to_string()), + 8 => Some("server_authz".to_string()), + 9 => Some("cert_type".to_string()), + 10 => Some("supported_groups".to_string()), + 11 => Some("ec_point_formats".to_string()), + 12 => Some("srp".to_string()), + 13 => Some("signature_algorithms".to_string()), + 14 => Some("use_srtp".to_string()), + 15 => Some("heartbeat".to_string()), + 16 => Some("alpn".to_string()), + 17 => Some("status_request_v2".to_string()), + 18 => Some("signed_certificate_timestamp".to_string()), + 19 => Some("client_certificate_type".to_string()), + 20 => Some("server_certificate_type".to_string()), + 21 => Some("padding".to_string()), + 22 => Some("encrypt_then_mac".to_string()), + 23 => Some("extended_master_secret".to_string()), + 24 => Some("token_binding".to_string()), + 25 => Some("cached_info".to_string()), + 26 => Some("tls_lts".to_string()), + 27 => Some("compress_certificate".to_string()), + 28 => Some("record_size_limit".to_string()), + 29 => Some("pwd_protect".to_string()), + 30 => Some("pwd_clear".to_string()), + 31 => Some("password_salt".to_string()), + 32 => Some("ticket_pinning".to_string()), + 33 => Some("tls_cert_with_extern_psk".to_string()), + 34 => Some("delegated_credentials".to_string()), + 35 => Some("session_ticket".to_string()), + 36 => Some("tlmsp".to_string()), + 37 => Some("tlmsp_proxying".to_string()), + 38 => Some("tlmsp_delegate".to_string()), + 39 => Some("supported_ekt_ciphers".to_string()), + 41 => Some("pre_shared_key".to_string()), + 42 => Some("early_data".to_string()), + 43 => Some("supported_versions".to_string()), + 44 => Some("cookie".to_string()), + 45 => Some("psk_key_exchange_modes".to_string()), + 47 => Some("certificate_authorities".to_string()), + 48 => Some("oid_filters".to_string()), + 49 => Some("post_handshake_auth".to_string()), + 50 => Some("signature_algorithms_cert".to_string()), + 51 => Some("key_share".to_string()), + 52 => Some("transparency_info".to_string()), + 53 => Some("connection_id_deprecated".to_string()), + 54 => Some("connection_id".to_string()), + 55 => Some("external_id_hash".to_string()), + 56 => Some("external_session_id".to_string()), + 57 => Some("quic_transport_parameters".to_string()), + 58 => Some("ticket_request".to_string()), + 59 => Some("dnssec_chain".to_string()), + 13172 => Some("next_protocol_negotiation".to_string()), + 65281 => Some("renegotiation_info".to_string()), + _ => None, + } +} + +fn log_template(tx: &QuicTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("quic")?; + if tx.header.ty != QuicType::Short { + js.set_string("version", String::from(tx.header.version).as_str())?; + + if let Some(sni) = &tx.sni { + js.set_string("sni", &String::from_utf8_lossy(sni))?; + } + if let Some(ua) = &tx.ua { + js.set_string("ua", &String::from_utf8_lossy(ua))?; + } + } + if !tx.cyu.is_empty() { + js.open_array("cyu")?; + for cyu in &tx.cyu { + js.start_object()?; + js.set_string("hash", &cyu.hash)?; + js.set_string("string", &cyu.string)?; + js.close()?; + } + js.close()?; + } + + if let Some(ja3) = &tx.ja3 { + if tx.client { + js.open_object("ja3")?; + } else { + js.open_object("ja3s")?; + } + let hash = format!("{:x}", Md5::new().chain(ja3).finalize()); + js.set_string("hash", &hash)?; + js.set_string("string", ja3)?; + js.close()?; + } + if !tx.extv.is_empty() { + js.open_array("extensions")?; + for e in &tx.extv { + js.start_object()?; + let etype = u16::from(e.etype); + if let Some(s) = quic_tls_extension_name(etype) { + js.set_string("name", &s)?; + } + js.set_uint("type", etype.into())?; + + if !e.values.is_empty() { + js.open_array("values")?; + for i in 0..e.values.len() { + js.append_string(&String::from_utf8_lossy(&e.values[i]))?; + } + js.close()?; + } + js.close()?; + } + js.close()?; + } + + js.close()?; + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_to_json( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, QuicTransaction); + log_template(tx, js).is_ok() +} diff --git a/rust/src/quic/mod.rs b/rust/src/quic/mod.rs new file mode 100644 index 0000000..8a8f1bb --- /dev/null +++ b/rust/src/quic/mod.rs @@ -0,0 +1,27 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! QUIC application layer, parser, detection and logger module. + +mod crypto; +mod cyu; +pub mod detect; +mod error; +mod frames; +mod logger; +mod parser; +pub mod quic; diff --git a/rust/src/quic/parser.rs b/rust/src/quic/parser.rs new file mode 100644 index 0000000..1269736 --- /dev/null +++ b/rust/src/quic/parser.rs @@ -0,0 +1,502 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ +use super::error::QuicError; +use super::frames::Frame; +use nom7::bytes::complete::take; +use nom7::combinator::{all_consuming, map}; +use nom7::number::complete::{be_u24, be_u32, be_u8}; +use nom7::IResult; +use std::convert::TryFrom; + +/* + gQUIC is the Google version of QUIC. + + The following docs were referenced when writing this parser + + References: + - https://docs.google.com/document/d/1WJvyZflAO2pq77yOLbp9NsGjC1CHetAXV8I0fQe-B_U/edit + - https://docs.google.com/document/d/1g5nIXAIkN_Y-7XJW5K45IblHd_L2f5LTaDUDwvZ5L6g/edit + - https://www.slideshare.net/shigeki_ohtsu/quic-overview + - https://tools.ietf.org/html/draft-ietf-quic-transport-17#section-19.8 + - https://github.com/salesforce/GQUIC_Protocol_Analyzer/blob/master/src/gquic-protocol.pac +*/ + +// List of accepted and tested quic versions format +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub struct QuicVersion(pub u32); + +impl QuicVersion { + pub const Q043: QuicVersion = QuicVersion(0x51303433); + pub const Q044: QuicVersion = QuicVersion(0x51303434); + pub const Q045: QuicVersion = QuicVersion(0x51303435); + pub const Q046: QuicVersion = QuicVersion(0x51303436); + pub const V2: QuicVersion = QuicVersion(0x6b3343cf); + + fn is_gquic(&self) -> bool { + *self == QuicVersion::Q043 + || *self == QuicVersion::Q044 + || *self == QuicVersion::Q045 + || *self == QuicVersion::Q046 + } +} + +impl From<QuicVersion> for String { + fn from(from: QuicVersion) -> Self { + match from { + QuicVersion(0x51303339) => "Q039".to_string(), + QuicVersion(0x51303433) => "Q043".to_string(), + QuicVersion(0x51303434) => "Q044".to_string(), + QuicVersion(0x51303435) => "Q045".to_string(), + QuicVersion(0x51303436) => "Q046".to_string(), + QuicVersion(0x6b3343cf) => "v2".to_string(), + QuicVersion(x) => format!("{:x}", x), + } + } +} +impl From<QuicVersion> for u32 { + fn from(from: QuicVersion) -> Self { + from.0 + } +} + +impl From<u32> for QuicVersion { + fn from(from: u32) -> Self { + QuicVersion(from) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub enum QuicType { + Initial, + Retry, + Handshake, + ZeroRTT, + VersionNegotiation, + Short, +} + +const QUIC_FLAG_MULTIPATH: u8 = 0x40; +const QUIC_FLAG_DCID_LEN: u8 = 0x8; +const QUIC_FLAG_NONCE: u8 = 0x4; +const QUIC_FLAG_VERSION: u8 = 0x1; + +#[derive(Debug, PartialEq, Eq)] +pub struct PublicFlags { + pub is_long: bool, + pub raw: u8, +} + +impl PublicFlags { + pub fn new(value: u8) -> Self { + let is_long = value & 0x80 == 0x80; + let raw = value; + + PublicFlags { is_long, raw } + } +} + +pub fn quic_pkt_num(input: &[u8]) -> u64 { + // There must be a more idiomatic way sigh... + match input.len() { + 1 => { + return input[0] as u64; + } + 2 => { + return ((input[0] as u64) << 8) | (input[1] as u64); + } + 3 => { + return ((input[0] as u64) << 16) | ((input[1] as u64) << 8) | (input[2] as u64); + } + 4 => { + return ((input[0] as u64) << 24) + | ((input[1] as u64) << 16) + | ((input[2] as u64) << 8) + | (input[3] as u64); + } + _ => { + // should not be reachable + debug_validate_fail!("Unexpected length for quic pkt num"); + return 0; + } + } +} + +pub fn quic_var_uint(input: &[u8]) -> IResult<&[u8], u64, QuicError> { + let (rest, first) = be_u8(input)?; + let msb = first >> 6; + let lsb = (first & 0x3F) as u64; + match msb { + 3 => { + // nom does not have be_u56 + let (rest, second) = be_u24(rest)?; + let (rest, third) = be_u32(rest)?; + return Ok((rest, (lsb << 56) | ((second as u64) << 32) | (third as u64))); + } + 2 => { + let (rest, second) = be_u24(rest)?; + return Ok((rest, (lsb << 24) | (second as u64))); + } + 1 => { + let (rest, second) = be_u8(rest)?; + return Ok((rest, (lsb << 8) | (second as u64))); + } + _ => { + // only remaining case is msb==0 + return Ok((rest, lsb)); + } + } +} + +/// A QUIC packet's header. +#[derive(Debug, PartialEq, Eq)] +pub struct QuicHeader { + pub flags: PublicFlags, + pub ty: QuicType, + pub version: QuicVersion, + pub version_buf: Vec<u8>, + pub dcid: Vec<u8>, + pub scid: Vec<u8>, + pub length: u16, +} + +#[derive(Debug, PartialEq)] +pub(crate) struct QuicData { + pub frames: Vec<Frame>, +} + +impl QuicHeader { + #[cfg(test)] + pub(crate) fn new( + flags: PublicFlags, ty: QuicType, version: QuicVersion, dcid: Vec<u8>, scid: Vec<u8>, + ) -> Self { + Self { + flags, + ty, + version, + version_buf: Vec::new(), + dcid, + scid, + length: 0, + } + } + + pub(crate) fn from_bytes( + input: &[u8], _dcid_len: usize, + ) -> IResult<&[u8], QuicHeader, QuicError> { + let (rest, first) = be_u8(input)?; + let flags = PublicFlags::new(first); + + if !flags.is_long && (flags.raw & QUIC_FLAG_MULTIPATH) == 0 { + let (mut rest, dcid) = if (flags.raw & QUIC_FLAG_DCID_LEN) != 0 { + take(8_usize)(rest)? + } else { + take(0_usize)(rest)? + }; + if (flags.raw & QUIC_FLAG_NONCE) != 0 && (flags.raw & QUIC_FLAG_DCID_LEN) == 0 { + let (rest1, _) = take(32_usize)(rest)?; + rest = rest1; + } + let version_buf: &[u8]; + let version; + if (flags.raw & QUIC_FLAG_VERSION) != 0 { + let (_, version_buf1) = take(4_usize)(rest)?; + let (rest1, version1) = map(be_u32, QuicVersion)(rest)?; + rest = rest1; + version = version1; + version_buf = version_buf1; + } else { + version = QuicVersion(0); + version_buf = &rest[..0]; + } + let pkt_num_len = 1 + ((flags.raw & 0x30) >> 4); + let (mut rest, _pkt_num) = take(pkt_num_len)(rest)?; + if (flags.raw & QUIC_FLAG_DCID_LEN) != 0 { + let (rest1, _msg_auth_hash) = take(12_usize)(rest)?; + rest = rest1; + } + let ty = if (flags.raw & QUIC_FLAG_VERSION) != 0 { + QuicType::Initial + } else { + QuicType::Short + }; + if let Ok(plength) = u16::try_from(rest.len()) { + return Ok(( + rest, + QuicHeader { + flags, + ty, + version, + version_buf: version_buf.to_vec(), + dcid: dcid.to_vec(), + scid: Vec::new(), + length: plength, + }, + )); + } else { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } else if !flags.is_long { + // Decode short header + // depends on version let (rest, dcid) = take(_dcid_len)(rest)?; + + if let Ok(plength) = u16::try_from(rest.len()) { + return Ok(( + rest, + QuicHeader { + flags, + ty: QuicType::Short, + version: QuicVersion(0), + version_buf: Vec::new(), + dcid: Vec::new(), + scid: Vec::new(), + length: plength, + }, + )); + } else { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } else { + // Decode Long header + let (_, version_buf) = take(4_usize)(rest)?; + let (rest, version) = map(be_u32, QuicVersion)(rest)?; + + let ty = if version == QuicVersion(0) { + QuicType::VersionNegotiation + } else { + // Q046 is when they started using IETF + if version.is_gquic() && version != QuicVersion::Q046 { + match first & 0x7f { + 0x7f => QuicType::Initial, + 0x7e => QuicType::Retry, + 0x7d => QuicType::Handshake, + 0x7c => QuicType::ZeroRTT, + _ => { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } + } else if version == QuicVersion::V2 { + match (first & 0x30) >> 4 { + 0x01 => QuicType::Initial, + 0x02 => QuicType::ZeroRTT, + 0x03 => QuicType::Handshake, + 0x00 => QuicType::Retry, + _ => { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } + } else { + // consider as Quic version 1 (and latest drafts) + match (first & 0x30) >> 4 { + 0x00 => QuicType::Initial, + 0x01 => QuicType::ZeroRTT, + 0x02 => QuicType::Handshake, + 0x03 => QuicType::Retry, + _ => { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } + } + }; + + let (rest, dcid, scid) = if version.is_gquic() { + // [DCID_LEN (4)][SCID_LEN (4)] + let (rest, lengths) = be_u8(rest)?; + + let mut dcid_len = (lengths & 0xF0) >> 4; + let mut scid_len = lengths & 0x0F; + + // Decode dcid length if not 0 + if dcid_len != 0 { + dcid_len += 3; + } + + // Decode scid length if not 0 + if scid_len != 0 { + scid_len += 3; + } + + let (rest, dcid) = take(dcid_len as usize)(rest)?; + let (rest, scid) = take(scid_len as usize)(rest)?; + + (rest, dcid.to_vec(), scid.to_vec()) + } else { + let (rest, dcid_len) = be_u8(rest)?; + let (rest, dcid) = take(dcid_len as usize)(rest)?; + + let (rest, scid_len) = be_u8(rest)?; + let (rest, scid) = take(scid_len as usize)(rest)?; + (rest, dcid.to_vec(), scid.to_vec()) + }; + + let mut has_length = false; + let rest = match ty { + QuicType::Initial => { + if version.is_gquic() { + let (rest, _pkt_num) = be_u32(rest)?; + let (rest, _msg_auth_hash) = take(12usize)(rest)?; + + rest + } else { + let (rest, token_length) = quic_var_uint(rest)?; + let (rest, _token) = take(token_length as usize)(rest)?; + has_length = true; + rest + } + } + _ => rest, + }; + let (rest, length) = if has_length { + let (rest2, plength) = quic_var_uint(rest)?; + if plength > rest2.len() as u64 { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + if let Ok(length) = u16::try_from(plength) { + (rest2, length) + } else { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + } + } else if let Ok(length) = u16::try_from(rest.len()) { + (rest, length) + } else { + return Err(nom7::Err::Error(QuicError::InvalidPacket)); + }; + + Ok(( + rest, + QuicHeader { + flags, + ty, + version, + version_buf: version_buf.to_vec(), + dcid, + scid, + length, + }, + )) + } + } +} + +impl QuicData { + pub(crate) fn from_bytes(input: &[u8]) -> Result<QuicData, QuicError> { + let (_, frames) = all_consuming(Frame::decode_frames)(input)?; + Ok(QuicData { frames }) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::quic::frames::{Frame, Stream, StreamTag}; + + #[test] + fn public_flags_test() { + let pf = PublicFlags::new(0xcb); + assert_eq!( + PublicFlags { + is_long: true, + raw: 0xcb + }, + pf + ); + } + + const TEST_DEFAULT_CID_LENGTH: usize = 8; + + #[test] + fn test_parse_gquic_unknown_version() { + // Test that we can parse unknown versions + let data = hex::decode("cbff00001d1091d0b10ac886039973885dfa07c469431409b15e86dd0990aaf906c5de620c4538398ffa58004482d2b19e732fc58818e57cb63569ce11abc00ea4fbac0725c5690a0838c27faf88663b48eca63baf0fba52af4eff7b4117384457c5cf1c1c0e52a1843c7676a4a35cf60c4c179e7186274c121110ace964771f31090f586b283bddbf82e9dd1d6a0e41fbaf243540dfb64f4543e1e87857c77cfc1ee9f883b97b89b6321ce30436119acfdbf2b31f4d0dbac0e5ea740ee59c8619d7c431320504c67f5c3aa9be5192f28ae378e0c8305fb95f01e7cb47c27f92cad7e8d55f699a41df3afe3894939f79e5f164771a6fe987602d975a06bfe8e6906b23601d08bcf2026eac25eca958a7b19ed7ba415e4d31b474264a479c53f01e1d35745ae62a9b148e39e2d7d33176f384d6ce4beb25d2177a8e0fbe5503ea034c9a645e5a8c98098bc5db4e11a351ac72b7079db1a858e11a6c6a4a1f44e1073903029cc08e82c48e6de00f5da7a546db371a4e49d4a213339ca074832cfeb4c39731f98a1683d7fb7db8a5b48c763440d6003fdfadd6a7fb23a62074064aafd585f6a887d5648ce71099d7d21e5cc1e14645f066016a7570d885bde4f239226884ee64fb8ec1218efec83d46ca104d9104bf46637ba3a3d8d6a88967859d60f46198e3a8495f2f211d717c6ca39987d2f4f971b502809932d973736dac67e5e28152c23d844d99fe7a5def822ca97aa433603423ee7fef57e6daa4579bb8f4f14a93663c54db415da5e8b9000d673d99c065c5922ba193eada475f2366b422d42dd86dd3b86fdef67d0e71cd200e3e24b77578f90e0e60717e3a1d6078b836d262028fc73efe7b3684b635f3001225acfd249fbe950dae7c539f015a0ce51c983c4d8e01d7e73e16946e681b2148d0ae4e72fb44d1048eb25572dae0a8016434b8c9e3fd3c93045b8afe67adc6cf7ce61a46819b712a8c24980e6c75bf007adf8910badfa102cd60c96238c8719b5e2b405905cfa6840176c7f71b7d9a2f480c36806f415b93b72821f0547b06f298584be093710a381fa352c34ba221cbcf1bbcd0b7d1aea354e460f6824df14d4bf4377a4503397e70f9993a55905ba298e798d9c69386eae8d0ebf6d871ff75e2d5a546bb8ee6ad9c92d88f950e2d8bc371aaad0d948e9f81c8151c51ee17c9257df4fd27cfeb9944b301a0fff1cb0a1b18836969457edd42f6ba370ecc2e5700bbb9fc15dc9f88c9bfc12c7dda64d423179c1eff8c53cca97056e09a07e29d02b4e553141b78d224cd79ae8056d923d41bc67eec00c943e3a62304487261d0877d54c40b7453c52e6c02141c2fa6601a357d53dddf39ae6e152501813e562a0613ca727ef3b0548c1f5a7e5036a8da84e166cec45de83bf217fb8f6c9a0ea20db0b16d1d2bb9e5e305e9d1f35e3065ab7188f79b9a841d1f6000ea744df1ba49116f7558feedf70677e35985d71b1c87c988d0b1ef2e436a54a77397546513c82bf307fc4b29152cafab11c8527eeda2addd00081c3b7b836a39920322a405c4e3774f20feda9998bf703fd10e93748b7834f3f3794d5b1f3f3099c608e84b025f5675b1526e8feee91ed04f4e91e37bd8e7089ec5a48edc2537bcddbd9d118d7937e2c25fa383186efd2f48fa3f5ebe7eaf544835bb330b61af1a95158c5e").unwrap(); + let (_rest, value) = + QuicHeader::from_bytes(data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap(); + assert_eq!( + QuicHeader { + flags: PublicFlags { + is_long: true, + raw: 0xcb + }, + ty: QuicType::Initial, + version: QuicVersion(0xff00001d), + version_buf: vec![0xff, 0x00, 0x00, 0x1d], + dcid: hex::decode("91d0b10ac886039973885dfa07c46943") + .unwrap() + .to_vec(), + scid: hex::decode("09b15e86dd0990aaf906c5de620c4538398ffa58") + .unwrap() + .to_vec(), + length: 1154, + }, + value + ); + } + + #[test] + fn test_parse_gquic_q044() { + let test_data = hex::decode("ff513034345005cad2cc06c4d0e400000001afac230bc5b56fb89800171b800143484c4f09000000504144008f030000534e490098030000564552009c03000043435300ac03000050444d44b00300004943534cb40300004d494453b803000043464357bc03000053464357c003000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003132372e302e302e310000000001e8816092921ae87eed8086a215829158353039803a09006400000000c0000000800000").unwrap(); + let (rest, header) = + QuicHeader::from_bytes(test_data.as_ref(), TEST_DEFAULT_CID_LENGTH).unwrap(); + + assert_eq!( + QuicHeader { + flags: PublicFlags { + is_long: true, + raw: 0xff + }, + ty: QuicType::Initial, + version: QuicVersion::Q044, + version_buf: vec![0x51, 0x30, 0x34, 0x34], + dcid: hex::decode("05cad2cc06c4d0e4").unwrap().to_vec(), + scid: Vec::new(), + length: 1042, + }, + header + ); + + let data = QuicData::from_bytes(rest).unwrap(); + assert_eq!( + QuicData { + frames: vec![Frame::Stream(Stream { + fin: false, + stream_id: vec![0x1], + offset: vec![], + tags: Some(vec![ + (StreamTag::Pad, [0x0; 911].to_vec()), + ( + StreamTag::Sni, + vec![0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31] + ), + (StreamTag::Ver, vec![0x0, 0x0, 0x0, 0x0]), + ( + StreamTag::Ccs, + vec![ + 0x1, 0xe8, 0x81, 0x60, 0x92, 0x92, 0x1a, 0xe8, 0x7e, 0xed, 0x80, + 0x86, 0xa2, 0x15, 0x82, 0x91 + ] + ), + (StreamTag::Pdmd, vec![0x58, 0x35, 0x30, 0x39]), + (StreamTag::Icsl, vec![0x80, 0x3a, 0x9, 0x0]), + (StreamTag::Mids, vec![0x64, 0x0, 0x0, 0x0]), + (StreamTag::Cfcw, vec![0x0, 0xc0, 0x0, 0x0]), + (StreamTag::Sfcw, vec![0x0, 0x80, 0x0, 0x0]), + ]) + })] + }, + data, + ); + } +} diff --git a/rust/src/quic/quic.rs b/rust/src/quic/quic.rs new file mode 100644 index 0000000..8e3ea6f --- /dev/null +++ b/rust/src/quic/quic.rs @@ -0,0 +1,502 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::{ + crypto::{quic_keys_initial, QuicKeys, AES128_KEY_LEN}, + cyu::Cyu, + frames::{Frame, QuicTlsExtension, StreamTag}, + parser::{quic_pkt_num, QuicData, QuicHeader, QuicType}, +}; +use crate::applayer::{self, *}; +use crate::core::{AppProto, Flow, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_UDP, Direction}; +use std::collections::VecDeque; +use std::ffi::CString; +use tls_parser::TlsExtensionType; + +static mut ALPROTO_QUIC: AppProto = ALPROTO_UNKNOWN; + +const DEFAULT_DCID_LEN: usize = 16; +const PKT_NUM_BUF_MAX_LEN: usize = 4; + +#[derive(FromPrimitive, Debug, AppLayerEvent)] +pub enum QuicEvent { + FailedDecrypt, + ErrorOnData, + ErrorOnHeader, +} + +#[derive(Debug)] +pub struct QuicTransaction { + tx_id: u64, + pub header: QuicHeader, + pub cyu: Vec<Cyu>, + pub sni: Option<Vec<u8>>, + pub ua: Option<Vec<u8>>, + pub extv: Vec<QuicTlsExtension>, + pub ja3: Option<String>, + pub client: bool, + tx_data: AppLayerTxData, +} + +impl QuicTransaction { + fn new( + header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>, + extv: Vec<QuicTlsExtension>, ja3: Option<String>, client: bool, + ) -> Self { + let direction = if client { Direction::ToServer } else { Direction::ToClient }; + let cyu = Cyu::generate(&header, &data.frames); + QuicTransaction { + tx_id: 0, + header, + cyu, + sni, + ua, + extv, + ja3, + client, + tx_data: AppLayerTxData::for_direction(direction), + } + } + + fn new_empty(client: bool, header: QuicHeader) -> Self { + let direction = if client { Direction::ToServer } else { Direction::ToClient }; + QuicTransaction { + tx_id: 0, + header, + cyu: Vec::new(), + sni: None, + ua: None, + extv: Vec::new(), + ja3: None, + client, + tx_data: AppLayerTxData::for_direction(direction), + } + } +} + +pub struct QuicState { + state_data: AppLayerStateData, + max_tx_id: u64, + keys: Option<QuicKeys>, + hello_tc: bool, + hello_ts: bool, + transactions: VecDeque<QuicTransaction>, +} + +impl Default for QuicState { + fn default() -> Self { + Self { + state_data: AppLayerStateData::new(), + max_tx_id: 0, + keys: None, + hello_tc: false, + hello_ts: false, + transactions: VecDeque::new(), + } + } +} + +impl QuicState { + fn new() -> Self { + Self::default() + } + + // Free a transaction by ID. + fn free_tx(&mut self, tx_id: u64) { + let tx = self + .transactions + .iter() + .position(|tx| tx.tx_id == tx_id + 1); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + fn get_tx(&mut self, tx_id: u64) -> Option<&QuicTransaction> { + self.transactions.iter().find(|&tx| tx.tx_id == tx_id + 1) + } + + fn new_tx( + &mut self, header: QuicHeader, data: QuicData, sni: Option<Vec<u8>>, ua: Option<Vec<u8>>, + extb: Vec<QuicTlsExtension>, ja3: Option<String>, client: bool, + ) { + let mut tx = QuicTransaction::new(header, data, sni, ua, extb, ja3, client); + self.max_tx_id += 1; + tx.tx_id = self.max_tx_id; + self.transactions.push_back(tx); + } + + fn tx_iterator( + &mut self, min_tx_id: u64, state: &mut u64, + ) -> Option<(&QuicTransaction, u64, bool)> { + let mut index = *state as usize; + let len = self.transactions.len(); + + while index < len { + let tx = &self.transactions[index]; + if tx.tx_id < min_tx_id + 1 { + index += 1; + continue; + } + *state = index as u64; + return Some((tx, tx.tx_id - 1, (len - index) > 1)); + } + + return None; + } + + fn decrypt<'a>( + &mut self, to_server: bool, header: &QuicHeader, framebuf: &'a [u8], buf: &'a [u8], + hlen: usize, output: &'a mut Vec<u8>, + ) -> Result<usize, ()> { + if let Some(keys) = &self.keys { + let hkey = if to_server { + &keys.remote.header + } else { + &keys.local.header + }; + if framebuf.len() < PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN { + return Err(()); + } + let h2len = hlen + usize::from(header.length); + let mut h2 = Vec::with_capacity(h2len); + h2.extend_from_slice(&buf[..h2len]); + let mut h20 = h2[0]; + let mut pktnum_buf = Vec::with_capacity(PKT_NUM_BUF_MAX_LEN); + pktnum_buf.extend_from_slice(&h2[hlen..hlen + PKT_NUM_BUF_MAX_LEN]); + let r1 = hkey.decrypt_in_place( + &h2[hlen + PKT_NUM_BUF_MAX_LEN..hlen + PKT_NUM_BUF_MAX_LEN + AES128_KEY_LEN], + &mut h20, + &mut pktnum_buf, + ); + if r1.is_err() { + return Err(()); + } + // mutate one at a time + h2[0] = h20; + let _ = &h2[hlen..hlen + 1 + ((h20 & 3) as usize)] + .copy_from_slice(&pktnum_buf[..1 + ((h20 & 3) as usize)]); + let pkt_num = quic_pkt_num(&h2[hlen..hlen + 1 + ((h20 & 3) as usize)]); + if framebuf.len() < 1 + ((h20 & 3) as usize) { + return Err(()); + } + output.extend_from_slice(&framebuf[1 + ((h20 & 3) as usize)..]); + let pkey = if to_server { + &keys.remote.packet + } else { + &keys.local.packet + }; + let r = pkey.decrypt_in_place(pkt_num, &h2[..hlen + 1 + ((h20 & 3) as usize)], output); + if let Ok(r2) = r { + return Ok(r2.len()); + } + } + return Err(()); + } + + fn handle_frames(&mut self, data: QuicData, header: QuicHeader, to_server: bool) { + let mut sni: Option<Vec<u8>> = None; + let mut ua: Option<Vec<u8>> = None; + let mut ja3: Option<String> = None; + let mut extv: Vec<QuicTlsExtension> = Vec::new(); + for frame in &data.frames { + match frame { + Frame::Stream(s) => { + if let Some(tags) = &s.tags { + for (tag, value) in tags { + if tag == &StreamTag::Sni { + sni = Some(value.to_vec()); + } else if tag == &StreamTag::Uaid { + ua = Some(value.to_vec()); + } + if sni.is_some() && ua.is_some() { + break; + } + } + } + } + Frame::Crypto(c) => { + ja3 = Some(c.ja3.clone()); + for e in &c.extv { + if e.etype == TlsExtensionType::ServerName && !e.values.is_empty() { + sni = Some(e.values[0].to_vec()); + } + } + extv.extend_from_slice(&c.extv); + if to_server { + self.hello_ts = true + } else { + self.hello_tc = true + } + } + _ => {} + } + } + self.new_tx(header, data, sni, ua, extv, ja3, to_server); + } + + fn set_event_notx(&mut self, event: QuicEvent, header: QuicHeader, client: bool) { + let mut tx = QuicTransaction::new_empty(client, header); + self.max_tx_id += 1; + tx.tx_id = self.max_tx_id; + tx.tx_data.set_event(event as u8); + self.transactions.push_back(tx); + } + + fn parse(&mut self, input: &[u8], to_server: bool) -> bool { + // so as to loop over multiple quic headers in one packet + let mut buf = input; + while !buf.is_empty() { + match QuicHeader::from_bytes(buf, DEFAULT_DCID_LEN) { + Ok((rest, header)) => { + if (to_server && self.hello_ts) || (!to_server && self.hello_tc) { + // payload is encrypted, stop parsing here + return true; + } + if header.ty == QuicType::Short { + // nothing to get + return true; + } + + // unprotect/decrypt packet + if self.keys.is_none() && header.ty == QuicType::Initial { + self.keys = quic_keys_initial(u32::from(header.version), &header.dcid); + } + // header.length was checked against rest.len() during parsing + let (mut framebuf, next_buf) = rest.split_at(header.length.into()); + let hlen = buf.len() - rest.len(); + let mut output; + if self.keys.is_some() { + output = Vec::with_capacity(framebuf.len() + 4); + if let Ok(dlen) = + self.decrypt(to_server, &header, framebuf, buf, hlen, &mut output) + { + output.resize(dlen, 0); + } else { + self.set_event_notx(QuicEvent::FailedDecrypt, header, to_server); + return false; + } + framebuf = &output; + } + buf = next_buf; + + if header.ty != QuicType::Initial { + // only version is interesting, no frames + self.new_tx( + header, + QuicData { frames: Vec::new() }, + None, + None, + Vec::new(), + None, + to_server, + ); + continue; + } + + match QuicData::from_bytes(framebuf) { + Ok(data) => { + self.handle_frames(data, header, to_server); + } + Err(_e) => { + self.set_event_notx(QuicEvent::ErrorOnData, header, to_server); + return false; + } + } + } + Err(_e) => { + // should we make an event with an empty header ? + return false; + } + } + } + return true; + } +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = QuicState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(unsafe { Box::from_raw(state as *mut QuicState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, QuicState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_probing_parser( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + let slice = build_slice!(input, input_len as usize); + + if QuicHeader::from_bytes(slice, DEFAULT_DCID_LEN).is_ok() { + return ALPROTO_QUIC; + } else { + return ALPROTO_FAILED; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_parse_tc( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, QuicState); + let buf = stream_slice.as_slice(); + + if state.parse(buf, false) { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_parse_ts( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, QuicState); + let buf = stream_slice.as_slice(); + + if state.parse(buf, true) { + return AppLayerResult::ok(); + } else { + return AppLayerResult::err(); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, QuicState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, QuicState); + return state.max_tx_id; +} + +#[no_mangle] +pub extern "C" fn rs_quic_state_progress_completion_status(_direction: u8) -> std::os::raw::c_int { + // This parser uses 1 to signal transaction completion status. + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let _tx = cast_pointer!(tx, QuicTransaction); + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_state_get_tx_iterator( + _ipproto: u8, _alproto: AppProto, state: *mut std::os::raw::c_void, min_tx_id: u64, + _max_tx_id: u64, istate: &mut u64, +) -> applayer::AppLayerGetTxIterTuple { + let state = cast_pointer!(state, QuicState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = tx as *const _ as *mut _; + let ires = applayer::AppLayerGetTxIterTuple::with_values(c_tx, out_tx_id, has_next); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +export_tx_data_get!(rs_quic_get_tx_data, QuicTransaction); +export_state_data_get!(rs_quic_get_state_data, QuicState); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"quic\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_quic_register_parser() { + let default_port = CString::new("[443,80]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_UDP, + probe_ts: Some(rs_quic_probing_parser), + probe_tc: Some(rs_quic_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_quic_state_new, + state_free: rs_quic_state_free, + tx_free: rs_quic_state_tx_free, + parse_ts: rs_quic_parse_ts, + parse_tc: rs_quic_parse_tc, + get_tx_count: rs_quic_state_get_tx_count, + get_tx: rs_quic_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_quic_tx_get_alstate_progress, + get_eventinfo: Some(QuicEvent::get_event_info), + get_eventinfo_byid: Some(QuicEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(rs_quic_state_get_tx_iterator), + get_tx_data: rs_quic_get_tx_data, + get_state_data: rs_quic_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("udp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_QUIC = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust quic parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for quic."); + } +} diff --git a/rust/src/rdp/error.rs b/rust/src/rdp/error.rs new file mode 100644 index 0000000..486d27a --- /dev/null +++ b/rust/src/rdp/error.rs @@ -0,0 +1,49 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> +// Author: Pierre Chifflier <chifflier@wzdftpd.net> +use nom7::error::{Error, ErrorKind, ParseError}; +use nom7::ErrorConvert; + +#[derive(Debug, PartialEq, Eq)] +pub enum RdpError { + UnimplementedLengthDeterminant, + NotX224Class0Error, + NomError(ErrorKind), +} + +impl<I> ParseError<I> for RdpError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + RdpError::NomError(kind) + } + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + RdpError::NomError(kind) + } +} + +impl From<Error<&[u8]>> for RdpError { + fn from(e: Error<&[u8]>) -> Self { + RdpError::NomError(e.code) + } +} + +impl ErrorConvert<RdpError> for ((&[u8], usize), ErrorKind) { + fn convert(self) -> RdpError { + RdpError::NomError(self.1) + } +} diff --git a/rust/src/rdp/log.rs b/rust/src/rdp/log.rs new file mode 100644 index 0000000..e0a71a8 --- /dev/null +++ b/rust/src/rdp/log.rs @@ -0,0 +1,597 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> + +use super::rdp::{RdpTransaction, RdpTransactionItem}; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::rdp::parser::*; +use crate::rdp::windows; +use x509_parser::prelude::{X509Certificate, FromDer}; + +#[no_mangle] +pub extern "C" fn rs_rdp_to_json(tx: &mut RdpTransaction, js: &mut JsonBuilder) -> bool { + log(tx, js).is_ok() +} + +/// populate a json object with transactional information, for logging +fn log(tx: &RdpTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("rdp")?; + js.set_uint("tx_id", tx.id)?; + + match &tx.item { + RdpTransactionItem::X224ConnectionRequest(ref x224) => x224_req_to_json(x224, js)?, + RdpTransactionItem::X224ConnectionConfirm(ref x224) => x224_conf_to_json(x224, js)?, + + RdpTransactionItem::McsConnectRequest(ref mcs) => { + mcs_req_to_json(mcs, js)?; + } + + RdpTransactionItem::McsConnectResponse(_) => { + // no additional JSON data beyond `event_type` + js.set_string("event_type", "connect_response")?; + } + + RdpTransactionItem::TlsCertificateChain(chain) => { + js.set_string("event_type", "tls_handshake")?; + js.open_array("x509_serials")?; + for blob in chain { + if let Ok((_, cert)) = X509Certificate::from_der(&blob.data) { + js.append_string(&cert.tbs_certificate.serial.to_str_radix(16))?; + } + } + js.close()?; + } + } + + js.close()?; + Ok(()) +} + +/// json helper for X224ConnectionRequest +fn x224_req_to_json(x224: &X224ConnectionRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { + use crate::rdp::parser::NegotiationRequestFlags as Flags; + + js.set_string("event_type", "initial_request")?; + if let Some(ref cookie) = x224.cookie { + js.set_string("cookie", &cookie.mstshash)?; + } + if let Some(ref req) = x224.negotiation_request { + if !req.flags.is_empty() { + js.open_array("flags")?; + if req.flags.contains(Flags::RESTRICTED_ADMIN_MODE_REQUIRED) { + js.append_string("restricted_admin_mode_required")?; + } + if req + .flags + .contains(Flags::REDIRECTED_AUTHENTICATION_MODE_REQUIRED) + { + js.append_string("redirected_authentication_mode_required")?; + } + if req.flags.contains(Flags::CORRELATION_INFO_PRESENT) { + js.append_string("correlation_info_present")?; + } + js.close()?; + } + } + + Ok(()) +} + +/// json helper for X224ConnectionConfirm +fn x224_conf_to_json(x224: &X224ConnectionConfirm, js: &mut JsonBuilder) -> Result<(), JsonError> { + use crate::rdp::parser::NegotiationResponseFlags as Flags; + + js.set_string("event_type", "initial_response")?; + if let Some(ref from_server) = x224.negotiation_from_server { + match &from_server { + NegotiationFromServer::Response(ref resp) => { + if !resp.flags.is_empty() { + js.open_array("server_supports")?; + if resp.flags.contains(Flags::EXTENDED_CLIENT_DATA_SUPPORTED) { + js.append_string("extended_client_data")?; + } + if resp.flags.contains(Flags::DYNVC_GFX_PROTOCOL_SUPPORTED) { + js.append_string("dynvc_gfx")?; + } + + // NEGRSP_FLAG_RESERVED not logged + + if resp.flags.contains(Flags::RESTRICTED_ADMIN_MODE_SUPPORTED) { + js.append_string("restricted_admin")?; + } + if resp + .flags + .contains(Flags::REDIRECTED_AUTHENTICATION_MODE_SUPPORTED) + { + js.append_string("redirected_authentication")?; + } + js.close()?; + } + + let protocol = match resp.protocol { + Protocol::ProtocolRdp => "rdp", + Protocol::ProtocolSsl => "ssl", + Protocol::ProtocolHybrid => "hybrid", + Protocol::ProtocolRdsTls => "rds_tls", + Protocol::ProtocolHybridEx => "hybrid_ex", + }; + js.set_string("protocol", protocol)?; + } + + NegotiationFromServer::Failure(ref fail) => match fail.code { + NegotiationFailureCode::SslRequiredByServer => { + js.set_uint( + "error_code", + NegotiationFailureCode::SslRequiredByServer as u64, + )?; + js.set_string("reason", "ssl required by server")?; + } + NegotiationFailureCode::SslNotAllowedByServer => { + js.set_uint( + "error_code", + NegotiationFailureCode::SslNotAllowedByServer as u64, + )?; + js.set_string("reason", "ssl not allowed by server")?; + } + NegotiationFailureCode::SslCertNotOnServer => { + js.set_uint( + "error_code", + NegotiationFailureCode::SslCertNotOnServer as u64, + )?; + js.set_string("reason", "ssl cert not on server")?; + } + NegotiationFailureCode::InconsistentFlags => { + js.set_uint( + "error_code", + NegotiationFailureCode::InconsistentFlags as u64, + )?; + js.set_string("reason", "inconsistent flags")?; + } + NegotiationFailureCode::HybridRequiredByServer => { + js.set_uint( + "error_code", + NegotiationFailureCode::HybridRequiredByServer as u64, + )?; + js.set_string("reason", "hybrid required by server")?; + } + NegotiationFailureCode::SslWithUserAuthRequiredByServer => { + js.set_uint( + "error_code", + NegotiationFailureCode::SslWithUserAuthRequiredByServer as u64, + )?; + js.set_string("reason", "ssl with user auth required by server")?; + } + }, + } + } + + Ok(()) +} + +/// json helper for McsConnectRequest +fn mcs_req_to_json(mcs: &McsConnectRequest, js: &mut JsonBuilder) -> Result<(), JsonError> { + // placeholder string value. We do not simply omit "unknown" values so that they can + // help indicate that a given enum may be out of date (new Windows version, etc.) + let unknown = String::from("unknown"); + + js.set_string("event_type", "connect_request")?; + for child in &mcs.children { + match child { + McsConnectRequestChild::CsClientCore(ref client) => { + js.open_object("client")?; + + match client.version { + Some(ref ver) => { + js.set_string("version", &version_to_string(ver, "v"))?; + } + None => { + js.set_string("version", &unknown)?; + } + } + + js.set_uint("desktop_width", client.desktop_width as u64)?; + js.set_uint("desktop_height", client.desktop_height as u64)?; + + if let Some(depth) = get_color_depth(client) { + js.set_uint("color_depth", depth)?; + } + + // sas_sequence not logged + + js.set_string( + "keyboard_layout", + &windows::lcid_to_string(client.keyboard_layout, &unknown), + )?; + + js.set_string( + "build", + &windows::os_to_string(&client.client_build, &unknown), + )?; + + if !client.client_name.is_empty() { + js.set_string("client_name", &client.client_name)?; + } + + if let Some(ref kb) = client.keyboard_type { + js.set_string("keyboard_type", &keyboard_to_string(kb))?; + } + + if client.keyboard_subtype != 0 { + js.set_uint("keyboard_subtype", client.keyboard_subtype as u64)?; + } + + if client.keyboard_function_key != 0 { + js.set_uint("function_keys", client.keyboard_function_key as u64)?; + } + + if !client.ime_file_name.is_empty() { + js.set_string("ime", &client.ime_file_name)?; + } + + // + // optional fields + // + + if let Some(id) = client.client_product_id { + js.set_uint("product_id", id as u64)?; + } + + if let Some(serial) = client.serial_number { + if serial != 0 { + js.set_uint("serial_number", serial as u64)?; + } + } + + // supported_color_depth not logged + + if let Some(ref early_capability_flags) = client.early_capability_flags { + use crate::rdp::parser::EarlyCapabilityFlags as Flags; + + if !early_capability_flags.is_empty() { + js.open_array("capabilities")?; + if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_ERRINFO_PDF) { + js.append_string("support_errinfo_pdf")?; + } + if early_capability_flags.contains(Flags::RNS_UD_CS_WANT_32BPP_SESSION) { + js.append_string("want_32bpp_session")?; + } + if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_STATUSINFO_PDU) + { + js.append_string("support_statusinfo_pdu")?; + } + if early_capability_flags.contains(Flags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS) + { + js.append_string("strong_asymmetric_keys")?; + } + + // RNS_UD_CS_UNUSED not logged + + if early_capability_flags.contains(Flags::RNS_UD_CS_VALID_CONNECTION_TYPE) { + js.append_string("valid_connection_type")?; + } + if early_capability_flags + .contains(Flags::RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU) + { + js.append_string("support_monitor_layout_pdu")?; + } + if early_capability_flags + .contains(Flags::RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT) + { + js.append_string("support_netchar_autodetect")?; + } + if early_capability_flags + .contains(Flags::RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL) + { + js.append_string("support_dynvc_gfx_protocol")?; + } + if early_capability_flags + .contains(Flags::RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE) + { + js.append_string("support_dynamic_time_zone")?; + } + if early_capability_flags.contains(Flags::RNS_UD_CS_SUPPORT_HEARTBEAT_PDU) { + js.append_string("support_heartbeat_pdu")?; + } + js.close()?; + } + } + + if let Some(ref id) = client.client_dig_product_id { + if !id.is_empty() { + js.set_string("id", id)?; + } + } + + if let Some(ref hint) = client.connection_hint { + let s = match hint { + ConnectionHint::ConnectionHintModem => "modem", + ConnectionHint::ConnectionHintBroadbandLow => "low_broadband", + ConnectionHint::ConnectionHintSatellite => "satellite", + ConnectionHint::ConnectionHintBroadbandHigh => "high_broadband", + ConnectionHint::ConnectionHintWan => "wan", + ConnectionHint::ConnectionHintLan => "lan", + ConnectionHint::ConnectionHintAutoDetect => "autodetect", + ConnectionHint::ConnectionHintNotProvided => "", + }; + if *hint != ConnectionHint::ConnectionHintNotProvided { + js.set_string("connection_hint", s)?; + } + } + + // server_selected_protocol not logged + + if let Some(width) = client.desktop_physical_width { + js.set_uint("physical_width", width as u64)?; + } + + if let Some(height) = client.desktop_physical_height { + js.set_uint("physical_height", height as u64)?; + } + + if let Some(orientation) = client.desktop_orientation { + js.set_uint("desktop_orientation", orientation as u64)?; + } + + if let Some(scale) = client.desktop_scale_factor { + js.set_uint("scale_factor", scale as u64)?; + } + + if let Some(scale) = client.device_scale_factor { + js.set_uint("device_scale_factor", scale as u64)?; + } + js.close()?; + } + + McsConnectRequestChild::CsNet(ref net) => { + if !net.channels.is_empty() { + js.open_array("channels")?; + for channel in &net.channels { + js.append_string(channel)?; + } + js.close()?; + } + } + + McsConnectRequestChild::CsUnknown(_) => {} + } + } + + Ok(()) +} + +/// converts RdpClientVersion to a string, using the provided prefix +fn version_to_string(ver: &RdpClientVersion, prefix: &str) -> String { + let mut result = String::from(prefix); + match ver { + RdpClientVersion::V4 => result.push('4'), + RdpClientVersion::V5_V8_1 => result.push('5'), + RdpClientVersion::V10_0 => result.push_str("10.0"), + RdpClientVersion::V10_1 => result.push_str("10.1"), + RdpClientVersion::V10_2 => result.push_str("10.2"), + RdpClientVersion::V10_3 => result.push_str("10.3"), + RdpClientVersion::V10_4 => result.push_str("10.4"), + RdpClientVersion::V10_5 => result.push_str("10.5"), + RdpClientVersion::V10_6 => result.push_str("10.6"), + RdpClientVersion::V10_7 => result.push_str("10.7"), + }; + result +} + +/// checks multiple client info fields to determine color depth +fn get_color_depth(client: &CsClientCoreData) -> Option<u64> { + // first check high_color_depth + match client.high_color_depth { + Some(HighColorDepth::HighColor4Bpp) => return Some(4), + Some(HighColorDepth::HighColor8Bpp) => return Some(8), + Some(HighColorDepth::HighColor15Bpp) => return Some(15), + Some(HighColorDepth::HighColor16Bpp) => return Some(16), + Some(HighColorDepth::HighColor24Bpp) => return Some(24), + _ => (), + }; + + // if not present, try post_beta2_color_depth + match client.post_beta2_color_depth { + Some(PostBeta2ColorDepth::RnsUdColor4Bpp) => return Some(4), + Some(PostBeta2ColorDepth::RnsUdColor8Bpp) => return Some(8), + Some(PostBeta2ColorDepth::RnsUdColor16Bpp555) => return Some(15), + Some(PostBeta2ColorDepth::RnsUdColor16Bpp565) => return Some(16), + Some(PostBeta2ColorDepth::RnsUdColor24Bpp) => return Some(24), + _ => (), + }; + + // if not present, try color_depth + match client.color_depth { + Some(ColorDepth::RnsUdColor4Bpp) => return Some(4), + Some(ColorDepth::RnsUdColor8Bpp) => return Some(8), + _ => return None, + } +} + +fn keyboard_to_string(kb: &KeyboardType) -> String { + let s = match kb { + KeyboardType::KbXt => "xt", + KeyboardType::KbIco => "ico", + KeyboardType::KbAt => "at", + KeyboardType::KbEnhanced => "enhanced", + KeyboardType::Kb1050 => "1050", + KeyboardType::Kb9140 => "9140", + KeyboardType::KbJapanese => "jp", + }; + String::from(s) +} + +#[cfg(test)] +mod tests { + use super::*; + + // for now, testing of JsonBuilder output is done by suricata-verify + + #[test] + fn test_version_string() { + assert_eq!("v10.7", version_to_string(&RdpClientVersion::V10_7, "v")); + } + + #[test] + fn test_color_depth_high() { + let core_data = CsClientCoreData { + version: None, + desktop_width: 1280, + desktop_height: 768, + color_depth: Some(ColorDepth::RnsUdColor4Bpp), + sas_sequence: None, + keyboard_layout: 0x409, + client_build: windows::OperatingSystem { + build: windows::Build::Win10_17763, + suffix: windows::Suffix::Rs5, + }, + client_name: String::from("SERVER-XYZ"), + keyboard_type: None, + keyboard_subtype: 0, + keyboard_function_key: 12, + ime_file_name: String::from(""), + post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp), + client_product_id: None, + serial_number: None, + high_color_depth: Some(HighColorDepth::HighColor24Bpp), + supported_color_depth: None, + early_capability_flags: None, + client_dig_product_id: None, + connection_hint: None, + server_selected_protocol: None, + desktop_physical_width: None, + desktop_physical_height: None, + desktop_orientation: None, + desktop_scale_factor: None, + device_scale_factor: None, + }; + assert_eq!(Some(24), get_color_depth(&core_data)); + } + + #[test] + fn test_color_depth_post_beta2() { + let core_data = CsClientCoreData { + version: None, + desktop_width: 1280, + desktop_height: 768, + color_depth: Some(ColorDepth::RnsUdColor4Bpp), + sas_sequence: None, + keyboard_layout: 0x409, + client_build: windows::OperatingSystem { + build: windows::Build::Win10_17763, + suffix: windows::Suffix::Rs5, + }, + client_name: String::from("SERVER-XYZ"), + keyboard_type: None, + keyboard_subtype: 0, + keyboard_function_key: 12, + ime_file_name: String::from(""), + post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp), + client_product_id: None, + serial_number: None, + high_color_depth: None, + supported_color_depth: None, + early_capability_flags: None, + client_dig_product_id: None, + connection_hint: None, + server_selected_protocol: None, + desktop_physical_width: None, + desktop_physical_height: None, + desktop_orientation: None, + desktop_scale_factor: None, + device_scale_factor: None, + }; + assert_eq!(Some(8), get_color_depth(&core_data)); + } + + #[test] + fn test_color_depth_basic() { + let core_data = CsClientCoreData { + version: None, + desktop_width: 1280, + desktop_height: 768, + color_depth: Some(ColorDepth::RnsUdColor4Bpp), + sas_sequence: None, + keyboard_layout: 0x409, + client_build: windows::OperatingSystem { + build: windows::Build::Win10_17763, + suffix: windows::Suffix::Rs5, + }, + client_name: String::from("SERVER-XYZ"), + keyboard_type: None, + keyboard_subtype: 0, + keyboard_function_key: 12, + ime_file_name: String::from(""), + post_beta2_color_depth: None, + client_product_id: None, + serial_number: None, + high_color_depth: None, + supported_color_depth: None, + early_capability_flags: None, + client_dig_product_id: None, + connection_hint: None, + server_selected_protocol: None, + desktop_physical_width: None, + desktop_physical_height: None, + desktop_orientation: None, + desktop_scale_factor: None, + device_scale_factor: None, + }; + assert_eq!(Some(4), get_color_depth(&core_data)); + } + + #[test] + fn test_color_depth_missing() { + let core_data = CsClientCoreData { + version: None, + desktop_width: 1280, + desktop_height: 768, + color_depth: None, + sas_sequence: None, + keyboard_layout: 0x409, + client_build: windows::OperatingSystem { + build: windows::Build::Win10_17763, + suffix: windows::Suffix::Rs5, + }, + client_name: String::from("SERVER-XYZ"), + keyboard_type: None, + keyboard_subtype: 0, + keyboard_function_key: 12, + ime_file_name: String::from(""), + post_beta2_color_depth: None, + client_product_id: None, + serial_number: None, + high_color_depth: None, + supported_color_depth: None, + early_capability_flags: None, + client_dig_product_id: None, + connection_hint: None, + server_selected_protocol: None, + desktop_physical_width: None, + desktop_physical_height: None, + desktop_orientation: None, + desktop_scale_factor: None, + device_scale_factor: None, + }; + assert!(get_color_depth(&core_data).is_none()); + } + + #[test] + fn test_keyboard_string() { + assert_eq!("enhanced", keyboard_to_string(&KeyboardType::KbEnhanced)); + } +} diff --git a/rust/src/rdp/mod.rs b/rust/src/rdp/mod.rs new file mode 100644 index 0000000..dc83db8 --- /dev/null +++ b/rust/src/rdp/mod.rs @@ -0,0 +1,27 @@ +/* Copyright (C) 2019 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. + */ + +//! RDP parser, logger and application layer module. +//! +//! written by Zach Kelly <zach.kelly@lmco.com> + +pub mod error; +pub mod log; +pub mod parser; +pub mod rdp; +pub mod util; +pub mod windows; diff --git a/rust/src/rdp/parser.rs b/rust/src/rdp/parser.rs new file mode 100644 index 0000000..a8004e2 --- /dev/null +++ b/rust/src/rdp/parser.rs @@ -0,0 +1,1431 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> + +//! RDP parser +//! +//! References: +//! * rdp-spec: <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/> +//! * t.123-spec: <https://www.itu.int/rec/T-REC-T.123-200701-I/en> +//! * t.125-spec: <https://www.itu.int/rec/T-REC-T.125-199802-I/en> +//! * x.224-spec: <https://www.itu.int/rec/T-REC-X.224-199511-I/en> +//! * x.691-spec: <https://www.itu.int/rec/T-REC-X.691/en> + +use crate::common::nom7::{bits, take_until_and_consume}; +use crate::rdp::error::RdpError; +use crate::rdp::util::{le_slice_to_string, parse_per_length_determinant, utf7_slice_to_string}; +use crate::rdp::windows; +use nom7::bits::streaming::take as take_bits; +use nom7::bytes::streaming::{tag, take}; +use nom7::combinator::{map, map_opt, map_res, opt, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::length_data; +use nom7::number::streaming::{be_u16, be_u8, le_u16, le_u32, le_u8}; +use nom7::sequence::tuple; +use nom7::{Err, IResult}; + +/// constrains dimension to a range, per spec +/// rdp-spec, section 2.2.1.3.2 Client Core Data +fn millimeters_to_opt(x: u32) -> Option<u32> { + if (10..=10_000).contains(&x) { + Some(x) + } else { + None + } +} + +/// constrains desktop scale to a range, per spec +/// rdp-spec, section 2.2.1.3.2 Client Core Data +fn desktop_scale_to_opt(x: u32) -> Option<u32> { + if (100..=500).contains(&x) { + Some(x) + } else { + None + } +} + +/// constrains device scale to a set of valid values, per spec +/// rdp-spec, section 2.2.1.3.2 Client Core Data +fn device_scale_to_opt(x: u32) -> Option<u32> { + if x == 100 || x == 140 || x == 180 { + Some(x) + } else { + None + } +} + +// ================ + +/// t.123-spec, section 8 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum TpktVersion { + T123 = 0x3, +} + +/// t.123-spec, section 8 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct T123Tpkt { + pub child: T123TpktChild, +} + +/// variants that a t.123 tpkt can hold +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum T123TpktChild { + X224ConnectionRequest(X224ConnectionRequest), + X224ConnectionConfirm(X224ConnectionConfirm), + Data(X223Data), + Raw(Vec<u8>), +} + +// ================ + +/// x.224-spec, sections 13.3.3, 13.4.3, 13.7.3 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum X224Type { + ConnectionConfirm = 0xd, + ConnectionRequest = 0xe, + _Data = 0xf, +} + +/// x.224-spec, section 13.3 +// rdp-spec, section 2.2.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct X224ConnectionRequest { + pub cdt: u8, + pub dst_ref: u16, + pub src_ref: u16, + pub class: u8, + pub options: u8, + pub cookie: Option<RdpCookie>, + pub negotiation_request: Option<NegotiationRequest>, + pub data: Vec<u8>, +} + +/// rdp-spec, section 2.2.1.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RdpCookie { + pub mstshash: String, +} + +/// rdp-spec, sections 2.2.1.1.1, 2.2.1.2.1, 2.2.1.2.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum X224ConnectionRequestType { + NegotiationRequest = 0x1, + NegotiationResponse = 0x2, + NegotiationFailure = 0x3, +} + +/// rdp-spec, section 2.2.1.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NegotiationRequest { + pub flags: NegotiationRequestFlags, + pub protocols: ProtocolFlags, +} + +// rdp-spec, section 2.2.1.1.1 +bitflags! { + #[derive(Default)] + pub struct NegotiationRequestFlags: u8 { + const RESTRICTED_ADMIN_MODE_REQUIRED = 0x1; + const REDIRECTED_AUTHENTICATION_MODE_REQUIRED = 0x2; + const CORRELATION_INFO_PRESENT = 0x8; + } +} + +/// rdp-spec, section 2.2.1.1.1 +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum Protocol { + ProtocolRdp = 0x0, + ProtocolSsl = 0x1, + ProtocolHybrid = 0x2, + ProtocolRdsTls = 0x4, + ProtocolHybridEx = 0x8, +} + +// rdp-spec, section 2.2.1.1.1 +bitflags! { + pub struct ProtocolFlags: u32 { + //Protocol::ProtocolRdp is 0 as always supported + //and bitflags crate does not like zero-bit flags + const PROTOCOL_SSL = Protocol::ProtocolSsl as u32; + const PROTOCOL_HYBRID = Protocol::ProtocolHybrid as u32; + const PROTOCOL_RDSTLS = Protocol::ProtocolRdsTls as u32; + const PROTOCOL_HYBRID_EX = Protocol::ProtocolHybridEx as u32; + } +} + +/// rdp-spec, section 2.2.1.2 +/// x.224-spec, section 13.3 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct X224ConnectionConfirm { + pub cdt: u8, + pub dst_ref: u16, + pub src_ref: u16, + pub class: u8, + pub options: u8, + pub negotiation_from_server: Option<NegotiationFromServer>, +} + +/// variants of a server negotiation +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NegotiationFromServer { + Response(NegotiationResponse), + Failure(NegotiationFailure), +} + +/// rdp-spec, section 2.2.1.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NegotiationResponse { + pub flags: NegotiationResponseFlags, + pub protocol: Protocol, +} + +// rdp-spec, section 2.2.1.2.1 +bitflags! { + #[derive(Default)] + pub struct NegotiationResponseFlags: u8 { + const EXTENDED_CLIENT_DATA_SUPPORTED = 0x1; + const DYNVC_GFX_PROTOCOL_SUPPORTED = 0x2; + const NEGRSP_FLAG_RESERVED = 0x4; + const RESTRICTED_ADMIN_MODE_SUPPORTED = 0x8; + const REDIRECTED_AUTHENTICATION_MODE_SUPPORTED = 0x10; + } +} + +/// rdp-spec, section 2.2.1.1.1 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NegotiationFailure { + pub code: NegotiationFailureCode, +} + +/// rdp-spec, section 2.2.1.2.2 +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum NegotiationFailureCode { + SslRequiredByServer = 0x1, + SslNotAllowedByServer = 0x2, + SslCertNotOnServer = 0x3, + InconsistentFlags = 0x4, + HybridRequiredByServer = 0x5, + SslWithUserAuthRequiredByServer = 0x6, +} + +// ================ + +/// x224-spec, section 13.7 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct X223Data { + pub child: X223DataChild, +} + +/// variants that an x.223 data message can hold +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum X223DataChild { + McsConnectRequest(McsConnectRequest), + McsConnectResponse(McsConnectResponse), + Raw(Vec<u8>), +} + +/// t.125-spec, section 7, part 2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum T125Type { + T125TypeMcsConnectRequest = 0x65, // 101 + T125TypeMcsConnectResponse = 0x66, // 102 +} + +/// rdp-spec, section 2.2.1.3.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct McsConnectRequest { + pub children: Vec<McsConnectRequestChild>, +} + +/// variants that an mcs connection message can hold +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum McsConnectRequestChild { + CsClientCore(CsClientCoreData), + CsNet(CsNet), + CsUnknown(CsUnknown), +} + +/// rdp-spec, section 2.2.1.3.1 +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum CsType { + Core = 0xc001, + Net = 0xc003, +} + +/// rdp-spec, section 2.2.1.3.2 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CsClientCoreData { + pub version: Option<RdpClientVersion>, + pub desktop_width: u16, + pub desktop_height: u16, + pub color_depth: Option<ColorDepth>, + pub sas_sequence: Option<SasSequence>, + pub keyboard_layout: u32, // see windows::lcid_to_string + pub client_build: windows::OperatingSystem, + pub client_name: String, + pub keyboard_type: Option<KeyboardType>, + pub keyboard_subtype: u32, + pub keyboard_function_key: u32, + pub ime_file_name: String, + // optional fields + pub post_beta2_color_depth: Option<PostBeta2ColorDepth>, + pub client_product_id: Option<u16>, + pub serial_number: Option<u32>, + pub high_color_depth: Option<HighColorDepth>, + pub supported_color_depth: Option<SupportedColorDepth>, + pub early_capability_flags: Option<EarlyCapabilityFlags>, + pub client_dig_product_id: Option<String>, + pub connection_hint: Option<ConnectionHint>, + pub server_selected_protocol: Option<ProtocolFlags>, + pub desktop_physical_width: Option<u32>, + pub desktop_physical_height: Option<u32>, + pub desktop_orientation: Option<DesktopOrientation>, + pub desktop_scale_factor: Option<u32>, + pub device_scale_factor: Option<u32>, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum RdpClientVersion { + V4 = 0x80001, + V5_V8_1 = 0x80004, + V10_0 = 0x80005, + V10_1 = 0x80006, + V10_2 = 0x80007, + V10_3 = 0x80008, + V10_4 = 0x80009, + V10_5 = 0x8000a, + V10_6 = 0x8000b, + V10_7 = 0x8000c, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum ColorDepth { + RnsUdColor4Bpp = 0xca00, + RnsUdColor8Bpp = 0xca01, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum SasSequence { + RnsUdSasDel = 0xaa03, +} + +// for keyboard layout, see windows::lcid_to_string + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum KeyboardType { + KbXt = 0x1, + KbIco = 0x2, + KbAt = 0x3, + KbEnhanced = 0x4, + Kb1050 = 0x5, + Kb9140 = 0x6, + KbJapanese = 0x7, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum PostBeta2ColorDepth { + RnsUdColorNotProvided = 0x0, + RnsUdColor4Bpp = 0xca00, + RnsUdColor8Bpp = 0xca01, + RnsUdColor16Bpp555 = 0xca02, + RnsUdColor16Bpp565 = 0xca03, + RnsUdColor24Bpp = 0xca04, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum HighColorDepth { + HighColorNotProvided = 0x0, + HighColor4Bpp = 0x4, + HighColor8Bpp = 0x8, + HighColor15Bpp = 0xf, + HighColor16Bpp = 0x10, + HighColor24Bpp = 0x18, +} + +// rdp-spec, section 2.2.1.3.2 Client Core Data +bitflags! { + #[derive(Default)] + pub struct SupportedColorDepth: u16 { + const RNS_UD_24_BPP_SUPPORT = 0x1; + const RNS_UD_16_BPP_SUPPORT = 0x2; + const RNS_UD_15_BPP_SUPPORT = 0x4; + const RNS_UD_32_BPP_SUPPORT = 0x8; + } +} + +// rdp-spec, section 2.2.1.3.2 Client Core Data +bitflags! { + #[derive(Default)] + pub struct EarlyCapabilityFlags: u16 { + const RNS_UD_CS_SUPPORT_ERRINFO_PDF = 0x1; + const RNS_UD_CS_WANT_32BPP_SESSION = 0x2; + const RNS_UD_CS_SUPPORT_STATUSINFO_PDU = 0x4; + const RNS_UD_CS_STRONG_ASYMMETRIC_KEYS = 0x8; + const RNS_UD_CS_UNUSED = 0x10; + const RNS_UD_CS_VALID_CONNECTION_TYPE = 0x20; + const RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU = 0x40; + const RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT = 0x80; + const RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL = 0x100; + const RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE = 0x200; + const RNS_UD_CS_SUPPORT_HEARTBEAT_PDU = 0x400; + } +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data, `connectionType` +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +pub enum ConnectionHint { + ConnectionHintNotProvided = 0x0, + ConnectionHintModem = 0x1, + ConnectionHintBroadbandLow = 0x2, + ConnectionHintSatellite = 0x3, + ConnectionHintBroadbandHigh = 0x4, + ConnectionHintWan = 0x5, + ConnectionHintLan = 0x6, + ConnectionHintAutoDetect = 0x7, +} + +/// rdp-spec, section 2.2.1.3.2 Client Core Data +#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq, Eq)] +pub enum DesktopOrientation { + OrientationLandscape = 0, + OrientationPortrait = 90, // 0x5a + OrientationLandscapeFlipped = 180, // 0xb4 + OrientationPortraitFlipped = 270, // 0x10e +} + +/// rdp-spec, section 2.2.1.3.4 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CsNet { + pub channels: Vec<String>, +} + +/// generic structure +/// cf. rdp-spec, section 2.2.1.3.4 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct CsUnknown { + pub typ: u16, + pub data: Vec<u8>, +} + +/// rdp-spec, section 2.2.1.4 +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct McsConnectResponse {} + +// ================== + +/// parser for t.123 and children +/// t.123-spec, section 8 +pub fn parse_t123_tpkt(input: &[u8]) -> IResult<&[u8], T123Tpkt, RdpError> { + let (i1, _version) = verify(be_u8, |&x| x == TpktVersion::T123 as u8)(input)?; + let (i2, _reserved) = be_u8(i1)?; + // less u8, u8, u16 + let (i3, sz) = map_opt(be_u16, |x: u16| x.checked_sub(4))(i2)?; + let (i4, data) = take(sz)(i3)?; + + let opt1: Option<T123TpktChild> = { + match opt(parse_x224_connection_request_class_0)(data) { + Ok((_remainder, opt)) => opt.map(T123TpktChild::X224ConnectionRequest), + Err(e) => return Err(e), + } + }; + + let opt2: Option<T123TpktChild> = match opt1 { + Some(x) => Some(x), + None => match opt(parse_x224_connection_confirm_class_0)(data) { + Ok((_remainder, opt)) => opt.map(T123TpktChild::X224ConnectionConfirm), + Err(e) => return Err(e), + }, + }; + + let opt3: Option<T123TpktChild> = match opt2 { + Some(x) => Some(x), + None => match opt(parse_x223_data_class_0)(data) { + Ok((_remainder, opt)) => opt.map(T123TpktChild::Data), + Err(e) => return Err(e), + }, + }; + let child: T123TpktChild = match opt3 { + Some(x) => x, + None => T123TpktChild::Raw(data.to_vec()), + }; + + return Ok((i4, T123Tpkt { child })); +} + +fn take_4_4_bits(input: &[u8]) -> IResult<&[u8], (u8, u8), RdpError> { + map(be_u8, |b| (b >> 4, b & 0xf))(input) +} + +fn parse_class_options(i: &[u8]) -> IResult<&[u8], (u8, u8)> { + bits( + tuple(( + verify(take_bits(4u8), |&x| x <= 4), + verify(take_bits(4u8), |&x| x <= 3) + )) + )(i) +} + +/// rdp-spec, section 2.2.1.1 +fn parse_x224_connection_request(input: &[u8]) -> IResult<&[u8], X224ConnectionRequest, RdpError> { + let (i1, length) = verify(be_u8, |&x| x != 0xff)(input)?; // 0xff is reserved + let (i2, cr_cdt) = take_4_4_bits(i1)?; + if cr_cdt.0 != X224Type::ConnectionRequest as u8 { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + if !(cr_cdt.1 == 0 || cr_cdt.1 == 1) { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + let (i3, dst_ref) = verify(be_u16, |&x| x == 0)(i2)?; + let (i4, src_ref) = be_u16(i3)?; + let (i5, class_options) = parse_class_options(i4).map_err(Err::convert)?; + // less cr_cdt (u8), dst_ref (u16), src_ref (u16), class_options (u8) + if length < 6 { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + let i6 = i5; + let sz = length - 6; + + // + // optionally find cookie and/or negotiation request + // + + let (i7, data) = { + if sz > 0 { + take(sz)(i6)? + } else { + (i6, &[][..]) + } + }; + + let (j1, cookie) = { + if !data.is_empty() { + match opt(parse_rdp_cookie)(data) { + Ok((remainder, opt)) => (remainder, opt), + Err(e) => return Err(e), + } + } else { + (&[][..], None) + } + }; + + let (j2, negotiation_request) = { + if !j1.is_empty() { + match opt(parse_negotiation_request)(j1) { + Ok((remainder, opt)) => (remainder, opt), + Err(e) => return Err(e), + } + } else { + (&[][..], None) + } + }; + + return Ok(( + i7, + X224ConnectionRequest { + cdt: cr_cdt.1, + dst_ref, + src_ref, + class: class_options.0, + options: class_options.1, + cookie, + negotiation_request, + data: j2.to_vec(), + }, + )); +} + +/// rdp-spec, section 2.2.1.1 +/// "An X.224 Class 0 Connection Request TPDU, as specified in [X224] section 13.3." +fn parse_x224_connection_request_class_0( + input: &[u8], +) -> IResult<&[u8], X224ConnectionRequest, RdpError> { + let (i1, x224) = parse_x224_connection_request(input)?; + if x224.class == 0 && x224.options == 0 { + Ok((i1, x224)) + } else { + Err(Err::Error(RdpError::NotX224Class0Error)) + } +} + +// rdp-spec, section 2.2.1.1.1 +fn parse_rdp_cookie(i: &[u8]) -> IResult<&[u8], RdpCookie, RdpError> { + let (i, _key) = tag(b"Cookie: ")(i)?; + let (i, _name) = tag(b"mstshash=")(i)?; + let (i, bytes) = take_until_and_consume(b"\r\n")(i)?; + // let (i, s) = map_res(value!(bytes), std::str::from_utf8)(i)?; + let s = std::str::from_utf8(bytes).map_err(|_| Err::Error(make_error(bytes, ErrorKind::MapRes)))?; + let cookie = RdpCookie{ mstshash: String::from(s) }; + Ok((i, cookie)) +} + +// rdp-spec, section 2.2.1.1.1 +fn parse_negotiation_request(i: &[u8]) -> IResult<&[u8], NegotiationRequest, RdpError> { + let (i, _typ) = verify(le_u8, |&x| x == X224ConnectionRequestType::NegotiationRequest as u8)(i)?; + let (i, flags) = map_opt(le_u8, NegotiationRequestFlags::from_bits)(i)?; + // u8, u8, u16, and u32 give _length of 8 + let (i, _length) = verify(le_u16, |&x| x == 8)(i)?; + let (i, protocols) = map_opt(le_u32, ProtocolFlags::from_bits)(i)?; + Ok((i, NegotiationRequest { flags, protocols })) +} + +/// rdp-spec, section 2.2.1.2 +/// x.224-spec, section 13.3 +fn parse_x224_connection_confirm(input: &[u8]) -> IResult<&[u8], X224ConnectionConfirm, RdpError> { + let (i1, length) = verify(be_u8, |&x| x != 0xff)(input)?; // 0xff is reserved + let (i2, cr_cdt) = take_4_4_bits(i1)?; + if cr_cdt.0 != X224Type::ConnectionConfirm as u8 { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + if !(cr_cdt.1 == 0 || cr_cdt.1 == 1) { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + let (i3, dst_ref) = verify(be_u16, |&x| x == 0)(i2)?; + let (i4, src_ref) = be_u16(i3)?; + let (i5, class_options) = parse_class_options(i4).map_err(Err::convert)?; + + // less cr_cdt (u8), dst_ref (u16), src_ref (u16), class_options (u8) + if length < 6 { + return Err(Err::Error(make_error(i1, ErrorKind::Verify))); + } + let i6 = i5; + let sz = length - 6; + + // a negotiation message from the server might be absent (sz == 0) + let (i7, negotiation_from_server) = { + if sz > 0 { + let (i7, data) = take(sz)(i6)?; + + // it will be one of a response message or a failure message + let opt1: Option<NegotiationFromServer> = match opt(parse_negotiation_response)(data) { + Ok((_remainder, opt)) => opt.map(NegotiationFromServer::Response), + Err(e) => return Err(e), + }; + let opt2: Option<NegotiationFromServer> = match opt1 { + Some(x) => Some(x), + None => match opt(parse_negotiation_failure)(data) { + Ok((_remainder, opt)) => opt.map(NegotiationFromServer::Failure), + Err(e) => return Err(e), + }, + }; + (i7, opt2) + } else { + (i6, None) + } + }; + + return Ok(( + i7, + X224ConnectionConfirm { + cdt: cr_cdt.1, + dst_ref, + src_ref, + class: class_options.0, + options: class_options.1, + negotiation_from_server, + }, + )); +} + +/// rdp-spec, section 2.2.1.2 +/// "An X.224 Class 0 Connection Confirm TPDU, as specified in [X224] section 13.4." +fn parse_x224_connection_confirm_class_0( + input: &[u8], +) -> IResult<&[u8], X224ConnectionConfirm, RdpError> { + let (i1, x224) = parse_x224_connection_confirm(input)?; + if x224.class == 0 && x224.options == 0 { + Ok((i1, x224)) + } else { + // x.224, but not a class 0 x.224 message + Err(Err::Error(RdpError::NotX224Class0Error)) + } +} + +// rdp-spec, section 2.2.1.1.1 +fn parse_negotiation_response(i: &[u8]) -> IResult<&[u8], NegotiationResponse, RdpError> { + let (i, _typ) = verify(le_u8, |&x| x == X224ConnectionRequestType::NegotiationResponse as u8)(i)?; + let (i, flags) = map_opt(le_u8, NegotiationResponseFlags::from_bits)(i)?; + // u8, u8, u16, and u32 give _length of 8 + let (i, _length) = verify(le_u16, |&x| x == 8)(i)?; + let (i, protocol) = map_opt(le_u32, num::FromPrimitive::from_u32)(i)?; + Ok((i, NegotiationResponse { flags, protocol })) +} + +// rdp-spec, section 2.2.1.1.1 +fn parse_negotiation_failure(i: &[u8]) -> IResult<&[u8], NegotiationFailure, RdpError> { + let (i, _typ) = verify(le_u8, |&x| x == X224ConnectionRequestType::NegotiationFailure as u8)(i)?; + let (i, _flags) = le_u8(i)?; + // u8, u8, u16, and u32 give _length of 8 + let (i, _length) = verify(le_u16, |&x| x == 8)(i)?; + let (i, code) = map_opt(le_u32, num::FromPrimitive::from_u32)(i)?; + Ok((i, NegotiationFailure { code })) +} + +/// x224-spec, section 13.7 +fn parse_x223_data_class_0(input: &[u8]) -> IResult<&[u8], X223Data, RdpError> { + fn parser(i: &[u8]) -> IResult<&[u8], (u8, u8, u8)> { + bits( + tuple(( + verify(take_bits(4u8), |&x| x == 0xf), + verify(take_bits(3u8), |&x| x == 0), + verify(take_bits(1u8), |&x| x == 0) + )) + )(i) + } + let (i1, _length) = verify(be_u8, |&x| x == 2)(input)?; + let (i2, _dt_x_roa) = parser(i1).map_err(Err::convert)?; + let (i3, _eot) = verify(be_u8, |&x| x == 0x80)(i2)?; + + // + // optionally find exactly one of the child messages + // + + let opt1: Option<X223DataChild> = match opt(parse_mcs_connect)(i3) { + Ok((_remainder, opt)) => opt.map(X223DataChild::McsConnectRequest), + Err(e) => return Err(e), + }; + + let opt2: Option<X223DataChild> = match opt1 { + Some(x) => Some(x), + None => match opt(parse_mcs_connect_response)(i3) { + Ok((_remainder, opt)) => opt.map(X223DataChild::McsConnectResponse), + Err(e) => return Err(e), + }, + }; + + let child: X223DataChild = match opt2 { + Some(x) => x, + None => X223DataChild::Raw(i3.to_vec()), + }; + + return Ok((&[], X223Data { child })); +} + +/// rdp-spec, section 2.2.1.3.2 +fn parse_mcs_connect(input: &[u8]) -> IResult<&[u8], McsConnectRequest, RdpError> { + let (i1, _ber_type) = verify( + le_u8, + // BER: 0b01=application, 0b1=non-primitive, 0b11111 + |&x| x == 0x7f + )(input)?; + let (i2, _t125_type) = verify(le_u8, |&x| x + == T125Type::T125TypeMcsConnectRequest as u8)(i1)?; + + // skip to, and consume, H.221 client-to-server key + let (i3, _skipped) = take_until_and_consume(b"Duca")(i2)?; + + let (i4, data) = length_data(parse_per_length_determinant)(i3)?; + let mut remainder: &[u8] = data; + let mut children = Vec::new(); + + // repeatedly attempt to parse optional CsClientCoreData, CsNet, and CsUnknown + // until data buffer is exhausted + loop { + remainder = match opt(parse_cs_client_core_data)(remainder) { + Ok((rem, o)) => match o { + // found CsClientCoreData + Some(core_data) => { + children.push(McsConnectRequestChild::CsClientCore(core_data)); + rem + } + None => match opt(parse_cs_net)(remainder) { + // found CsNet + Ok((rem, o)) => match o { + Some(net) => { + children.push(McsConnectRequestChild::CsNet(net)); + rem + } + None => { + match opt(parse_cs_unknown)(remainder) { + // was able to parse CsUnknown + Ok((rem, o)) => match o { + Some(unknown) => { + children.push(McsConnectRequestChild::CsUnknown(unknown)); + rem + } + None => { + break; + } + }, + Err(Err::Incomplete(i)) => { + return Err(Err::Incomplete(i)) + } + Err(Err::Failure(_)) | Err(Err::Error(_)) => break, + } + } + }, + Err(Err::Incomplete(i)) => return Err(Err::Incomplete(i)), + Err(Err::Failure(_)) | Err(Err::Error(_)) => break, + }, + }, + Err(Err::Incomplete(i)) => return Err(Err::Incomplete(i)), + Err(Err::Failure(_)) | Err(Err::Error(_)) => break, + }; + if remainder.is_empty() { + break; + } + } + + return Ok((i4, McsConnectRequest { children })); +} + +/// rdp-spec, section 2.2.1.3.2 +fn parse_cs_client_core_data(input: &[u8]) -> IResult<&[u8], CsClientCoreData> { + let (i1, _typ) = verify(le_u16, |&x| x == CsType::Core as u16)(input)?; + // less u16, u16 + let (i2, sz) = map_opt(le_u16, |x: u16| x.checked_sub(4))(i1)?; + let (i3, data) = take(sz)(i2)?; + let (j1, version) = map(le_u32, num::FromPrimitive::from_u32)(data)?; + let (j2, desktop_width) = le_u16(j1)?; + let (j3, desktop_height) = le_u16(j2)?; + let (j4, color_depth) = map(le_u16, num::FromPrimitive::from_u16)(j3)?; + let (j5, sas_sequence) = map(le_u16, num::FromPrimitive::from_u16)(j4)?; + let (j6, keyboard_layout) = le_u32(j5)?; + let (j7, client_build) = map(le_u32, windows::build_number_to_os)(j6)?; + let (j8, client_name) = map_res(take(32_usize), le_slice_to_string)(j7)?; + let (j9, keyboard_type) = map(le_u32, num::FromPrimitive::from_u32)(j8)?; + let (j10, keyboard_subtype) = le_u32(j9)?; + let (j11, keyboard_function_key) = le_u32(j10)?; + let (j12, ime_file_name) = map_res(take(64_usize), le_slice_to_string)(j11)?; + + // + // optional fields below (but each requires the previous) + // + + let (j13, post_beta2_color_depth) = + match opt(map_opt(le_u16, num::FromPrimitive::from_u16))(j12) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j12, None), + }; + + let (j14, client_product_id) = match post_beta2_color_depth { + None => (j13, None), + Some(_) => match opt(le_u16)(j13) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j13, None), + }, + }; + + let (j15, serial_number) = match client_product_id { + None => (j14, None), + Some(_) => match opt(le_u32)(j14) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j14, None), + }, + }; + + let (j16, high_color_depth) = match serial_number { + None => (j15, None), + Some(_) => { + match opt(map_opt(le_u16, num::FromPrimitive::from_u16))(j15) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j15, None), + } + } + }; + + let (j17, supported_color_depth) = match high_color_depth { + None => (j16, None), + Some(_) => { + match opt(map_opt(le_u16, SupportedColorDepth::from_bits))(j16) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j16, None), + } + } + }; + + let (j18, early_capability_flags) = match supported_color_depth { + None => (j17, None), + Some(_) => { + match opt(map_opt(le_u16, EarlyCapabilityFlags::from_bits))(j17) as IResult<&[u8], _> + { + Ok((rem, obj)) => (rem, obj), + _ => (j17, None), + } + } + }; + + let (j19, client_dig_product_id) = match early_capability_flags { + None => (j18, None), + Some(_) => { + match opt(map_res(take(64usize), le_slice_to_string))(j18) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j18, None), + } + } + }; + + let (j20, connection_hint) = match client_dig_product_id { + None => (j19, None), + Some(_) => { + match opt(map_opt(le_u8, num::FromPrimitive::from_u8))(j19) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j19, None), + } + } + }; + + let (j21, pad) = match connection_hint { + None => (j20, None), + Some(_) => match opt(take(1usize))(j20) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j20, None), + }, + }; + + let (j22, server_selected_protocol) = match pad { + None => (j21, None), + Some(_) => { + match opt(map_opt(le_u32, ProtocolFlags::from_bits))(j21) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j21, None), + } + } + }; + + let (j23, desktop_physical_width) = match server_selected_protocol { + None => (j22, None), + Some(_) => match opt(map_opt(le_u32, millimeters_to_opt))(j22) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j22, None), + }, + }; + + let (j24, desktop_physical_height) = match desktop_physical_width { + None => (j23, None), + Some(_) => match opt(map_opt(le_u32, millimeters_to_opt))(j23) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j23, None), + }, + }; + + let (j25, desktop_orientation) = match desktop_physical_height { + None => (j24, None), + Some(_) => { + match opt(map_opt(le_u16, num::FromPrimitive::from_u16))(j24) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j24, None), + } + } + }; + + let (j26, desktop_scale_factor) = match desktop_orientation { + None => (j25, None), + Some(_) => match opt(map_opt(le_u32, desktop_scale_to_opt))(j25) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j25, None), + }, + }; + + let (_j27, device_scale_factor) = match desktop_scale_factor { + None => (j26, None), + Some(_) => match opt(map_opt(le_u32, device_scale_to_opt))(j26) as IResult<&[u8], _> { + Ok((rem, obj)) => (rem, obj), + _ => (j26, None), + }, + }; + + return Ok(( + i3, + CsClientCoreData { + version, + desktop_width, + desktop_height, + color_depth, + sas_sequence, + keyboard_layout, + client_build, + client_name, + keyboard_type, + keyboard_subtype, + keyboard_function_key, + ime_file_name, + post_beta2_color_depth, + client_product_id, + serial_number, + high_color_depth, + supported_color_depth, + early_capability_flags, + client_dig_product_id, + connection_hint, + server_selected_protocol, + desktop_physical_width, + desktop_physical_height, + desktop_orientation, + desktop_scale_factor, + device_scale_factor, + }, + )); +} + +/// rdp-spec, section 2.2.1.3.4 +fn parse_cs_net(input: &[u8]) -> IResult<&[u8], CsNet> { + let (i1, _typ) = verify(le_u16, |&x| x == CsType::Net as u16)(input)?; + // less _typ (u16), this length indicator (u16), count (u32) + let (i2, sz) = map_opt(le_u16, |x: u16| x.checked_sub(8))(i1)?; + let (i3, count) = le_u32(i2)?; + let (i4, data) = take(sz)(i3)?; + + let mut remainder: &[u8] = data; + let mut channels = Vec::new(); + + for _index in 0..count { + // a channel name is 8 bytes, section 2.2.1.3.4.1 + let (j1, name) = map_res(take(8_usize), utf7_slice_to_string)(remainder)?; + channels.push(name); + // options (u32) are discarded for now + let (j2, _options) = le_u32(j1)?; + remainder = j2; + } + + return Ok((i4, CsNet { channels })); +} + +// generic CS structure parse +// cf. rdp-spec, section 2.2.1.3.4 +fn parse_cs_unknown(i: &[u8]) -> IResult<&[u8], CsUnknown> { + let (i, typ) = map_opt(le_u16, |x| { + let opt: Option<CsType> = num::FromPrimitive::from_u16(x); + match opt { + // an unknown type must not be present in CsType + Some(_) => None, + None => Some(x), + } + })(i)?; + // less u16, u16 + let (i, sz) = map_opt(le_u16, |x: u16| x.checked_sub(4))(i)?; + let (i, data) = take(sz)(i)?; + Ok((i, CsUnknown { typ, data: data.to_vec() })) +} + +// rdp-spec, section 2.2.1.4 +fn parse_mcs_connect_response(i: &[u8]) -> IResult<&[u8], McsConnectResponse, RdpError> { + let (i, _ber_type) = verify( + le_u8, + // BER: 0b01=application, 0b1=non-primitive, 0b11111 + |&x| x == 0x7f)(i)?; + let (i, _t125_type) = verify(le_u8, |&x| x == T125Type::T125TypeMcsConnectResponse as u8)(i)?; + Ok((i, McsConnectResponse {})) +} + +#[cfg(test)] +mod tests_cookie_21182 { + use crate::rdp::parser::*; + + static BYTES: [u8; 37] = [ + 0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6f, 0x6b, + 0x69, 0x65, 0x3a, 0x20, 0x6d, 0x73, 0x74, 0x73, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x75, 0x73, + 0x65, 0x72, 0x31, 0x32, 0x33, 0x0d, 0x0a, + ]; + + #[test] + fn test_t123_x224_cookie() { + let t123_bytes = &BYTES[..]; + let t123_tpkt: T123Tpkt = T123Tpkt { + child: T123TpktChild::X224ConnectionRequest(X224ConnectionRequest { + cdt: 0, + dst_ref: 0, + src_ref: 0, + class: 0, + options: 0, + cookie: Some(RdpCookie { + mstshash: String::from("user123"), + }), + negotiation_request: None, + data: Vec::new(), + }), + }; + assert_eq!(Ok((&[][..], t123_tpkt)), parse_t123_tpkt(t123_bytes)); + } +} + +#[cfg(test)] +mod tests_negotiate_49350 { + use crate::rdp::parser::*; + + static BYTES: [u8; 20] = [ + 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xff, + ]; + static TPKT_BEGIN: usize = 0; + static X224_BEGIN: usize = TPKT_BEGIN + 4; + static NEG_REQ_BEGIN: usize = X224_BEGIN + 7; + static NEG_REQ_END: usize = NEG_REQ_BEGIN + 8; + static X224_END: usize = NEG_REQ_END; + static TPKT_END: usize = X224_END; + static PADDING_BEGIN: usize = TPKT_END; + + #[test] + fn test_t123_x224_negotiate() { + let t123_bytes = &BYTES[TPKT_BEGIN..]; + let t123_tpkt: T123Tpkt = T123Tpkt { + child: T123TpktChild::X224ConnectionRequest(X224ConnectionRequest { + cdt: 0, + dst_ref: 0, + src_ref: 0, + class: 0, + options: 0, + cookie: None, + negotiation_request: Some(NegotiationRequest { + flags: NegotiationRequestFlags::empty(), + protocols: ProtocolFlags { bits: Protocol::ProtocolRdp as u32 }, + }), + data: Vec::new(), + }), + }; + assert_eq!( + Ok((&BYTES[PADDING_BEGIN..][..], t123_tpkt)), + parse_t123_tpkt(t123_bytes) + ) + } +} + +#[cfg(test)] +mod tests_core_49350 { + use crate::rdp::parser::*; + + static BYTES: [u8; 429] = [ + 0x03, 0x00, 0x01, 0xac, 0x02, 0xf0, 0x80, 0x7f, 0x65, 0x82, 0x01, 0xa0, 0x04, 0x01, 0x01, + 0x04, 0x01, 0x01, 0x01, 0x01, 0xff, 0x30, 0x19, 0x02, 0x01, 0x22, 0x02, 0x01, 0x02, 0x02, + 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, + 0x02, 0x01, 0x02, 0x30, 0x19, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0x04, 0x20, 0x02, 0x01, 0x02, + 0x30, 0x1c, 0x02, 0x02, 0xff, 0xff, 0x02, 0x02, 0xfc, 0x17, 0x02, 0x02, 0xff, 0xff, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02, + 0x04, 0x82, 0x01, 0x3f, 0x00, 0x05, 0x00, 0x14, 0x7c, 0x00, 0x01, 0x81, 0x36, 0x00, 0x08, + 0x00, 0x10, 0x00, 0x01, 0xc0, 0x00, 0x44, 0x75, 0x63, 0x61, 0x81, 0x28, 0x01, 0xc0, 0xd8, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x03, 0x01, 0xca, 0x03, 0xaa, 0x09, 0x04, + 0x00, 0x00, 0x71, 0x17, 0x00, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, + 0x00, 0x52, 0x00, 0x2d, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xca, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0xc0, 0x0c, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xc0, 0x0c, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x38, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x64, 0x70, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x72, 0x64, 0x70, 0x73, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x64, + 0x72, 0x64, 0x79, 0x6e, 0x76, 0x63, 0x00, 0x00, 0x00, 0x80, 0xc0, 0x63, 0x6c, 0x69, 0x70, + 0x72, 0x64, 0x72, 0x00, 0x00, 0x00, 0xa0, 0xc0, 0xff, + ]; + static TPKT_BEGIN: usize = 0; + static X223_BEGIN: usize = TPKT_BEGIN + 4; + static MCS_CONNECT_BEGIN: usize = X223_BEGIN + 3; + static MCS_CONNECT_END: usize = MCS_CONNECT_BEGIN + 421; + static X223_END: usize = MCS_CONNECT_END; + static TPKT_END: usize = X223_END; + static PADDING_BEGIN: usize = TPKT_END; + + #[test] + fn test_t123_x223_connect_core() { + let t123_bytes = &BYTES[TPKT_BEGIN..]; + let core_data = CsClientCoreData { + version: Some(RdpClientVersion::V5_V8_1), + desktop_width: 1280, + desktop_height: 768, + color_depth: Some(ColorDepth::RnsUdColor8Bpp), + sas_sequence: Some(SasSequence::RnsUdSasDel), + keyboard_layout: 0x409, + client_build: windows::OperatingSystem { + build: windows::Build::Vista_6001, + suffix: windows::Suffix::Sp1, + }, + client_name: String::from("SERVER-XYZ"), + keyboard_type: Some(KeyboardType::KbEnhanced), + keyboard_subtype: 0, + keyboard_function_key: 12, + ime_file_name: String::from(""), + post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp), + client_product_id: Some(1), + serial_number: Some(0), + high_color_depth: Some(HighColorDepth::HighColor8Bpp), + supported_color_depth: Some( + SupportedColorDepth::RNS_UD_15_BPP_SUPPORT + | SupportedColorDepth::RNS_UD_16_BPP_SUPPORT + | SupportedColorDepth::RNS_UD_24_BPP_SUPPORT + | SupportedColorDepth::RNS_UD_32_BPP_SUPPORT, + ), + early_capability_flags: Some( + EarlyCapabilityFlags::RNS_UD_CS_SUPPORT_ERRINFO_PDF + | EarlyCapabilityFlags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS, + ), + client_dig_product_id: Some(String::from("")), + connection_hint: Some(ConnectionHint::ConnectionHintNotProvided), + server_selected_protocol: Some(ProtocolFlags { bits: Protocol::ProtocolRdp as u32 }), + desktop_physical_width: None, + desktop_physical_height: None, + desktop_orientation: None, + desktop_scale_factor: None, + device_scale_factor: None, + }; + let mut children = Vec::new(); + children.push(McsConnectRequestChild::CsClientCore(core_data)); + children.push(McsConnectRequestChild::CsUnknown(CsUnknown { + typ: 0xc004, + data: BYTES[0x160..0x160 + 0x8].to_vec(), + })); + children.push(McsConnectRequestChild::CsUnknown(CsUnknown { + typ: 0xc002, + data: BYTES[0x16c..0x16c + 0x8].to_vec(), + })); + let mut channels = Vec::new(); + channels.push(String::from("rdpdr")); + channels.push(String::from("rdpsnd")); + channels.push(String::from("drdynvc")); + channels.push(String::from("cliprdr")); + children.push(McsConnectRequestChild::CsNet(CsNet { channels })); + let t123_tpkt: T123Tpkt = T123Tpkt { + child: T123TpktChild::Data(X223Data { + child: X223DataChild::McsConnectRequest(McsConnectRequest { children }), + }), + }; + assert_eq!( + Ok((&BYTES[PADDING_BEGIN..][..], t123_tpkt)), + parse_t123_tpkt(t123_bytes) + ); + } +} + +#[cfg(test)] +mod tests_x223_response_49350 { + use crate::rdp::parser::*; + + // changed offset 9 from 0x65 to 0x66 so it is no longer an mcs connect + static BYTES: [u8; 9] = [0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0x66]; + + #[test] + fn test_x223_response() { + let t123_bytes = &BYTES[..]; + assert_eq!( + Ok(( + &[][..], + T123Tpkt { + child: T123TpktChild::Data(X223Data { + child: X223DataChild::McsConnectResponse(McsConnectResponse {}), + }) + } + )), + parse_t123_tpkt(t123_bytes) + ) + } +} + +#[cfg(test)] +mod tests_t123_raw_49350 { + use crate::rdp::parser::*; + + // changed offset 4 from 0x02 to 0x03 so it is no longer an X223 data object + static BYTES: [u8; 9] = [0x03, 0x00, 0x00, 0x09, 0x03, 0xf0, 0x80, 0x7f, 0x65]; + + #[test] + fn test_t123_raw() { + let t123_bytes = &BYTES[..]; + assert_eq!( + Ok(( + &[][..], + T123Tpkt { + child: T123TpktChild::Raw(BYTES[4..].to_vec()) + } + )), + parse_t123_tpkt(t123_bytes) + ) + } +} + +#[cfg(test)] +mod tests_x224_raw_49350 { + use crate::rdp::parser::*; + + // changed offset 11 from 0x01 to 0x02 so it is not a known X224 payload type + static BYTES: [u8; 19] = [ + 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + + #[test] + fn test_x224_raw() { + let t123_bytes = &BYTES[..]; + assert_eq!( + Ok(( + &[][..], + T123Tpkt { + child: T123TpktChild::X224ConnectionRequest(X224ConnectionRequest { + cdt: 0, + dst_ref: 0, + src_ref: 0, + class: 0, + options: 0, + cookie: None, + negotiation_request: None, + data: BYTES[11..].to_vec(), + }) + } + )), + parse_t123_tpkt(t123_bytes) + ) + } +} + +#[cfg(test)] +mod tests_x223_raw_49350 { + use crate::rdp::parser::*; + + // changed offset 9 from 0x65 to 0xff so it is no longer an mcs connect + static BYTES: [u8; 9] = [0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0xff]; + + #[test] + fn test_x223_raw() { + let t123_bytes = &BYTES[..]; + assert_eq!( + Ok(( + &[][..], + T123Tpkt { + child: T123TpktChild::Data(X223Data { + child: X223DataChild::Raw(BYTES[7..].to_vec()), + }) + } + )), + parse_t123_tpkt(t123_bytes) + ) + } +} + +#[cfg(test)] +mod tests_negotiate_incomplete_49350 { + use crate::rdp::parser::*; + use nom7::Needed; + + static BYTES: [u8; 19] = [ + 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, + 0x00, 0x00, 0x00, 0x00, + ]; + static TPKT_BEGIN: usize = 0; + static X224_BEGIN: usize = TPKT_BEGIN + 4; + static NEG_REQ_BEGIN: usize = X224_BEGIN + 7; + static NEG_REQ_END: usize = NEG_REQ_BEGIN + 8; + static X224_END: usize = NEG_REQ_END; + static TPKT_END: usize = X224_END; + + #[test] + fn test_t123_incomplete() { + let t123_bytes = &BYTES[TPKT_BEGIN..TPKT_END - 1]; + assert_eq!( + // fails: map_opt!(i2, be_u16, |x: u16| x.checked_sub(4))? + Err(Err::Incomplete(Needed::new(1))), + parse_t123_tpkt(t123_bytes) + ) + } + + #[test] + fn test_x224_incomplete() { + let x224_bytes = &BYTES[X224_BEGIN..X224_END - 1]; + assert_eq!( + // fails: expr_opt!(i5, length.checked_sub(6))? + // not counting a u8 length read, which was also successful + Err(Err::Incomplete(Needed::new( 1))), + parse_x224_connection_request_class_0(x224_bytes) + ) + } + + #[test] + fn test_negotiate_incomplete() { + let neg_req_bytes = &BYTES[NEG_REQ_BEGIN..NEG_REQ_END - 1]; + assert_eq!( + // fails: map_opt!(le_u32, num::FromPrimitive::from_u32)? + Err(Err::Incomplete(Needed::new(1))), + parse_negotiation_request(neg_req_bytes) + ) + } +} + +#[cfg(test)] +mod tests_core_incomplete_49350 { + use crate::rdp::parser::*; + use nom7::Needed; + + static BYTES: [u8; 428] = [ + 0x03, 0x00, 0x01, 0xac, 0x02, 0xf0, 0x80, 0x7f, 0x65, 0x82, 0x01, 0xa0, 0x04, 0x01, 0x01, + 0x04, 0x01, 0x01, 0x01, 0x01, 0xff, 0x30, 0x19, 0x02, 0x01, 0x22, 0x02, 0x01, 0x02, 0x02, + 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, + 0x02, 0x01, 0x02, 0x30, 0x19, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0x04, 0x20, 0x02, 0x01, 0x02, + 0x30, 0x1c, 0x02, 0x02, 0xff, 0xff, 0x02, 0x02, 0xfc, 0x17, 0x02, 0x02, 0xff, 0xff, 0x02, + 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02, + 0x04, 0x82, 0x01, 0x3f, 0x00, 0x05, 0x00, 0x14, 0x7c, 0x00, 0x01, 0x81, 0x36, 0x00, 0x08, + 0x00, 0x10, 0x00, 0x01, 0xc0, 0x00, 0x44, 0x75, 0x63, 0x61, 0x81, 0x28, 0x01, 0xc0, 0xd8, + 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x03, 0x01, 0xca, 0x03, 0xaa, 0x09, 0x04, + 0x00, 0x00, 0x71, 0x17, 0x00, 0x00, 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, + 0x00, 0x52, 0x00, 0x2d, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xca, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x08, 0x00, 0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x04, 0xc0, 0x0c, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0xc0, 0x0c, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xc0, 0x38, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x64, 0x70, 0x64, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x80, 0x72, 0x64, 0x70, 0x73, 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x64, + 0x72, 0x64, 0x79, 0x6e, 0x76, 0x63, 0x00, 0x00, 0x00, 0x80, 0xc0, 0x63, 0x6c, 0x69, 0x70, + 0x72, 0x64, 0x72, 0x00, 0x00, 0x00, 0xa0, 0xc0, + ]; + static X223_BEGIN: usize = 4; + static MCS_CONNECT_BEGIN: usize = X223_BEGIN + 3; + static MCS_CONNECT_END: usize = MCS_CONNECT_BEGIN + 421; + static _X223_END: usize = MCS_CONNECT_END; + + #[test] + fn test_x223_incomplete() { + let x223_bytes = &BYTES[X223_BEGIN..X223_BEGIN + 2]; + assert_eq!( + // fails: verify!(i2, be_u8, |x| x == 0x80)? + Err(Err::Incomplete(Needed::new(1))), + parse_x223_data_class_0(x223_bytes) + ) + } + + #[test] + fn test_connect_incomplete() { + let connect_bytes = &BYTES[MCS_CONNECT_BEGIN..MCS_CONNECT_END - 1]; + assert_eq!( + // fails: length_data!(i3, parse_per_length_determinant)? + // which reads the length (2) but not the full data (0x128) + Err(Err::Incomplete(Needed::new(1))), + parse_mcs_connect(connect_bytes) + ) + } +} diff --git a/rust/src/rdp/rdp.rs b/rust/src/rdp/rdp.rs new file mode 100644 index 0000000..f08026a --- /dev/null +++ b/rust/src/rdp/rdp.rs @@ -0,0 +1,667 @@ +/* Copyright (C) 2019-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> + +//! RDP application layer + +use crate::applayer::{self, *}; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use crate::rdp::parser::*; +use nom7::Err; +use std; +use std::collections::VecDeque; +use tls_parser::{parse_tls_plaintext, TlsMessage, TlsMessageHandshake, TlsRecordType}; + +static mut ALPROTO_RDP: AppProto = ALPROTO_UNKNOWN; + +// +// transactions +// + +#[derive(Debug, PartialEq, Eq)] +pub struct CertificateBlob { + pub data: Vec<u8>, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum RdpTransactionItem { + X224ConnectionRequest(X224ConnectionRequest), + X224ConnectionConfirm(X224ConnectionConfirm), + McsConnectRequest(McsConnectRequest), + McsConnectResponse(McsConnectResponse), + TlsCertificateChain(Vec<CertificateBlob>), +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RdpTransaction { + pub id: u64, + pub item: RdpTransactionItem, + // managed by macros `export_tx_get_detect_state!` and `export_tx_set_detect_state!` + tx_data: AppLayerTxData, +} + +impl Transaction for RdpTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl RdpTransaction { + fn new(id: u64, item: RdpTransactionItem) -> Self { + Self { + id, + item, + tx_data: AppLayerTxData::new(), + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, RdpState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, RdpState); + return state.next_id; +} + +#[no_mangle] +pub extern "C" fn rs_rdp_tx_get_progress( + _tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + // tx complete when `rs_rdp_tx_get_progress(...) == rs_rdp_tx_get_progress_complete(...)` + // here, all transactions are immediately complete on insert + return 1; +} + +// +// state +// + +#[derive(Debug, PartialEq, Eq)] +pub struct RdpState { + state_data: AppLayerStateData, + next_id: u64, + transactions: VecDeque<RdpTransaction>, + tls_parsing: bool, + bypass_parsing: bool, +} + +impl State<RdpTransaction> for RdpState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&RdpTransaction> { + self.transactions.get(index) + } +} + +impl RdpState { + fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + next_id: 0, + transactions: VecDeque::new(), + tls_parsing: false, + bypass_parsing: false, + } + } + + fn free_tx(&mut self, tx_id: u64) { + let len = self.transactions.len(); + let mut found = false; + let mut index = 0; + for ii in 0..len { + let tx = &self.transactions[ii]; + if tx.id == tx_id { + found = true; + index = ii; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + fn get_tx(&self, tx_id: u64) -> Option<&RdpTransaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id) + } + + fn new_tx(&mut self, item: RdpTransactionItem) -> RdpTransaction { + self.next_id += 1; + let tx = RdpTransaction::new(self.next_id, item); + return tx; + } + + /// parse buffer captures from client to server + fn parse_ts(&mut self, input: &[u8]) -> AppLayerResult { + // no need to process input buffer + if self.bypass_parsing { + return AppLayerResult::ok(); + } + let mut available = input; + + loop { + if available.is_empty() { + return AppLayerResult::ok(); + } + if self.tls_parsing { + match parse_tls_plaintext(available) { + Ok((remainder, _tls)) => { + // bytes available for further parsing are what remain + available = remainder; + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + return AppLayerResult::err(); + } + } + } else { + // every message should be encapsulated within a T.123 tpkt + match parse_t123_tpkt(available) { + // success + Ok((remainder, t123)) => { + // bytes available for further parsing are what remain + available = remainder; + // evaluate message within the tpkt + match t123.child { + // X.224 connection request + T123TpktChild::X224ConnectionRequest(x224) => { + let tx = + self.new_tx(RdpTransactionItem::X224ConnectionRequest(x224)); + self.transactions.push_back(tx); + } + + // X.223 data packet, evaluate what it encapsulates + T123TpktChild::Data(x223) => { + #[allow(clippy::single_match)] + match x223.child { + X223DataChild::McsConnectRequest(mcs) => { + let tx = + self.new_tx(RdpTransactionItem::McsConnectRequest(mcs)); + self.transactions.push_back(tx); + } + // unknown message in X.223, skip + _ => (), + } + } + + // unknown message in T.123, skip + _ => (), + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + if probe_tls_handshake(available) { + self.tls_parsing = true; + let r = self.parse_ts(available); + if r.status == 1 { + //adds bytes already consumed to incomplete result + let consumed = (input.len() - available.len()) as u32; + return AppLayerResult::incomplete(r.consumed + consumed, r.needed); + } else { + return r; + } + } else { + return AppLayerResult::err(); + } + } + } + } + } + } + + /// parse buffer captures from server to client + fn parse_tc(&mut self, input: &[u8]) -> AppLayerResult { + // no need to process input buffer + if self.bypass_parsing { + return AppLayerResult::ok(); + } + let mut available = input; + + loop { + if available.is_empty() { + return AppLayerResult::ok(); + } + if self.tls_parsing { + match parse_tls_plaintext(available) { + Ok((remainder, tls)) => { + // bytes available for further parsing are what remain + available = remainder; + for message in &tls.msg { + #[allow(clippy::single_match)] + match message { + TlsMessage::Handshake(TlsMessageHandshake::Certificate( + contents, + )) => { + let mut chain = Vec::new(); + for cert in &contents.cert_chain { + chain.push(CertificateBlob { + data: cert.data.to_vec(), + }); + } + let tx = + self.new_tx(RdpTransactionItem::TlsCertificateChain(chain)); + self.transactions.push_back(tx); + self.bypass_parsing = true; + } + _ => {} + } + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + return AppLayerResult::err(); + } + } + } else { + // every message should be encapsulated within a T.123 tpkt + match parse_t123_tpkt(available) { + // success + Ok((remainder, t123)) => { + // bytes available for further parsing are what remain + available = remainder; + // evaluate message within the tpkt + match t123.child { + // X.224 connection confirm + T123TpktChild::X224ConnectionConfirm(x224) => { + let tx = + self.new_tx(RdpTransactionItem::X224ConnectionConfirm(x224)); + self.transactions.push_back(tx); + } + + // X.223 data packet, evaluate what it encapsulates + T123TpktChild::Data(x223) => { + #[allow(clippy::single_match)] + match x223.child { + X223DataChild::McsConnectResponse(mcs) => { + let tx = self + .new_tx(RdpTransactionItem::McsConnectResponse(mcs)); + self.transactions.push_back(tx); + self.bypass_parsing = true; + return AppLayerResult::ok(); + } + + // unknown message in X.223, skip + _ => (), + } + } + + // unknown message in T.123, skip + _ => (), + } + } + + Err(Err::Incomplete(_)) => { + // nom need not compatible with applayer need, request one more byte + return AppLayerResult::incomplete( + (input.len() - available.len()) as u32, + (available.len() + 1) as u32, + ); + } + + Err(Err::Failure(_)) | Err(Err::Error(_)) => { + if probe_tls_handshake(available) { + self.tls_parsing = true; + let r = self.parse_tc(available); + if r.status == 1 { + //adds bytes already consumed to incomplete result + let consumed = (input.len() - available.len()) as u32; + return AppLayerResult::incomplete(r.consumed + consumed, r.needed); + } else { + return r; + } + } else { + return AppLayerResult::err(); + } + } + } + } + } + } +} + +#[no_mangle] +pub extern "C" fn rs_rdp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = RdpState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_rdp_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut RdpState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, RdpState); + state.free_tx(tx_id); +} + +// +// probe +// + +/// probe for T.123 type identifier, as each message is encapsulated in T.123 +fn probe_rdp(input: &[u8]) -> bool { + !input.is_empty() && input[0] == TpktVersion::T123 as u8 +} + +/// probe for T.123 message, whether to client or to server +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_probe_ts_tc( + _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, +) -> AppProto { + if !input.is_null() { + // probe bytes for `rdp` protocol pattern + let slice = build_slice!(input, input_len as usize); + + // Some sessions immediately (first byte) switch to TLS/SSL, e.g. + // https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=view&target=rdp-ssl.pcap.gz + // but this callback will not be exercised, so `probe_tls_handshake` not needed here. + if probe_rdp(slice) { + return ALPROTO_RDP; + } + } + return ALPROTO_UNKNOWN; +} + +/// probe for TLS +fn probe_tls_handshake(input: &[u8]) -> bool { + !input.is_empty() && input[0] == u8::from(TlsRecordType::Handshake) +} + +// +// parse +// + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_parse_ts( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = cast_pointer!(state, RdpState); + let buf = stream_slice.as_slice(); + // attempt to parse bytes as `rdp` protocol + return state.parse_ts(buf); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_parse_tc( + _flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = cast_pointer!(state, RdpState); + let buf = stream_slice.as_slice(); + // attempt to parse bytes as `rdp` protocol + return state.parse_tc(buf); +} + +export_tx_data_get!(rs_rdp_get_tx_data, RdpTransaction); +export_state_data_get!(rs_rdp_get_state_data, RdpState); + +// +// registration +// + +const PARSER_NAME: &[u8] = b"rdp\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_rdp_register_parser() { + let default_port = std::ffi::CString::new("[3389]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_rdp_probe_ts_tc), + probe_tc: Some(rs_rdp_probe_ts_tc), + min_depth: 0, + max_depth: 16, + state_new: rs_rdp_state_new, + state_free: rs_rdp_state_free, + tx_free: rs_rdp_state_tx_free, + parse_ts: rs_rdp_parse_ts, + parse_tc: rs_rdp_parse_tc, + get_tx_count: rs_rdp_state_get_tx_count, + get_tx: rs_rdp_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_rdp_tx_get_progress, + get_eventinfo: None, + get_eventinfo_byid: None, + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<RdpState, RdpTransaction>), + get_tx_data: rs_rdp_get_tx_data, + get_state_data: rs_rdp_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = std::ffi::CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_RDP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rdp::parser::{RdpCookie, X224ConnectionRequest}; + + #[test] + fn test_probe_rdp() { + let buf: &[u8] = &[0x03, 0x00]; + assert!(probe_rdp(buf)); + } + + #[test] + fn test_probe_rdp_other() { + let buf: &[u8] = &[0x04, 0x00]; + assert!(!probe_rdp(buf)); + } + + #[test] + fn test_probe_tls_handshake() { + let buf: &[u8] = &[0x16, 0x00]; + assert!(probe_tls_handshake(buf)); + } + + #[test] + fn test_probe_tls_handshake_other() { + let buf: &[u8] = &[0x17, 0x00]; + assert!(!probe_tls_handshake(buf)); + } + + #[test] + fn test_parse_ts_rdp() { + let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00]; + let buf_2: &[u8] = &[ + 0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6f, + 0x6b, 0x69, 0x65, 0x3a, 0x20, 0x6d, 0x73, 0x74, 0x73, 0x68, 0x61, 0x73, 0x68, 0x3d, + 0x75, 0x73, 0x65, 0x72, 0x31, 0x32, 0x33, 0x0d, 0x0a, + ]; + let mut state = RdpState::new(); + // will consume 0, request length + 1 + assert_eq!(AppLayerResult::incomplete(0, 9), state.parse_ts(buf_1)); + assert_eq!(0, state.transactions.len()); + // exactly aligns with transaction + assert_eq!(AppLayerResult::ok(), state.parse_ts(buf_2)); + assert_eq!(1, state.transactions.len()); + let item = RdpTransactionItem::X224ConnectionRequest(X224ConnectionRequest { + cdt: 0, + dst_ref: 0, + src_ref: 0, + class: 0, + options: 0, + cookie: Some(RdpCookie { + mstshash: String::from("user123"), + }), + negotiation_request: None, + data: Vec::new(), + }); + assert_eq!(item, state.transactions[0].item); + } + + #[test] + fn test_parse_ts_other() { + let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00]; + let mut state = RdpState::new(); + assert_eq!(AppLayerResult::err(), state.parse_ts(buf)); + } + + #[test] + fn test_parse_tc_rdp() { + let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02]; + let buf_2: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0x66]; + let mut state = RdpState::new(); + // will consume 0, request length + 1 + assert_eq!(AppLayerResult::incomplete(0, 6), state.parse_tc(buf_1)); + assert_eq!(0, state.transactions.len()); + // exactly aligns with transaction + assert_eq!(AppLayerResult::ok(), state.parse_tc(buf_2)); + assert_eq!(1, state.transactions.len()); + let item = RdpTransactionItem::McsConnectResponse(McsConnectResponse {}); + assert_eq!(item, state.transactions[0].item); + } + + #[test] + fn test_parse_tc_other() { + let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00]; + let mut state = RdpState::new(); + assert_eq!(AppLayerResult::err(), state.parse_tc(buf)); + } + + #[test] + fn test_state_new_tx() { + let mut state = RdpState::new(); + let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let tx0 = state.new_tx(item0); + let tx1 = state.new_tx(item1); + assert_eq!(2, state.next_id); + state.transactions.push_back(tx0); + state.transactions.push_back(tx1); + assert_eq!(2, state.transactions.len()); + assert_eq!(1, state.transactions[0].id); + assert_eq!(2, state.transactions[1].id); + assert!(!state.tls_parsing); + assert!(!state.bypass_parsing); + } + + #[test] + fn test_state_get_tx() { + let mut state = RdpState::new(); + let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let tx0 = state.new_tx(item0); + let tx1 = state.new_tx(item1); + let tx2 = state.new_tx(item2); + state.transactions.push_back(tx0); + state.transactions.push_back(tx1); + state.transactions.push_back(tx2); + assert_eq!(Some(&state.transactions[1]), state.get_tx(2)); + } + + #[test] + fn test_state_free_tx() { + let mut state = RdpState::new(); + let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest { + children: Vec::new(), + }); + let tx0 = state.new_tx(item0); + let tx1 = state.new_tx(item1); + let tx2 = state.new_tx(item2); + state.transactions.push_back(tx0); + state.transactions.push_back(tx1); + state.transactions.push_back(tx2); + state.free_tx(1); + assert_eq!(3, state.next_id); + assert_eq!(2, state.transactions.len()); + assert_eq!(2, state.transactions[0].id); + assert_eq!(3, state.transactions[1].id); + assert_eq!(None, state.get_tx(1)); + } +} diff --git a/rust/src/rdp/util.rs b/rust/src/rdp/util.rs new file mode 100644 index 0000000..a4228f2 --- /dev/null +++ b/rust/src/rdp/util.rs @@ -0,0 +1,175 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> + +use crate::rdp::error::RdpError; +use byteorder::ReadBytesExt; +use memchr::memchr; +use nom7::{Err, IResult, Needed}; +use std::io::Cursor; +use widestring::U16CString; + +/// converts a raw u8 slice of little-endian wide chars into a String +pub fn le_slice_to_string(input: &[u8]) -> Result<String, Box<dyn std::error::Error>> { + let mut vec = Vec::new(); + let mut cursor = Cursor::new(input); + while let Ok(x) = cursor.read_u16::<byteorder::LittleEndian>() { + if x == 0 { + break; + } + vec.push(x); + } + match U16CString::new(vec) { + Ok(x) => match x.to_string() { + Ok(x) => Ok(x), + Err(e) => Err(e.into()), + }, + Err(e) => Err(e.into()), + } +} + +/// converts a raw u8 slice of null-padded utf7 chars into a String, dropping the nulls +pub fn utf7_slice_to_string(input: &[u8]) -> Result<String, Box<dyn std::error::Error>> { + let s = match memchr(b'\0', input) { + Some(end) => &input[..end], + None => input, + }; + match std::str::from_utf8(s) { + Ok(s) => Ok(String::from(s)), + Err(e) => Err(e.into()), + } +} + +/// parses a PER length determinant, to determine the length of the data following +/// x.691-spec: section 10.9 +pub fn parse_per_length_determinant(input: &[u8]) -> IResult<&[u8], u32, RdpError> { + if input.is_empty() { + // need a single byte to begin length determination + Err(Err::Incomplete(Needed::new(1))) + } else { + let bit7 = input[0] >> 7; + match bit7 { + 0b0 => { + // byte starts with 0b0. Length stored in the lower 7 bits of the current byte + let length = input[0] as u32 & 0x7f; + Ok((&input[1..], length)) + } + _ => { + let bit6 = input[0] >> 6 & 0x1; + match bit6 { + 0b0 => { + // byte starts with 0b10. Length stored in the remaining 6 bits and the next byte + if input.len() < 2 { + Err(Err::Incomplete(Needed::new(2))) + } else { + let length = ((input[0] as u32 & 0x3f) << 8) | input[1] as u32; + Ok((&input[2..], length)) + } + } + _ => { + // byte starts with 0b11. Without an example to confirm 16K+ lengths are properly + // handled, leaving this branch unimplemented + Err(Err::Error(RdpError::UnimplementedLengthDeterminant)) + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::rdp::error::RdpError; + use nom7::Needed; + + #[test] + fn test_le_string_abc() { + let abc = &[0x41, 0x00, 0x42, 0x00, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00]; + assert_eq!(String::from("ABC"), le_slice_to_string(abc).unwrap()); + } + + #[test] + fn test_le_string_empty() { + let empty = &[]; + assert_eq!(String::from(""), le_slice_to_string(empty).unwrap()); + } + + #[test] + fn test_le_string_invalid() { + let not_utf16le = &[0x00, 0xd8, 0x01, 0x00]; + assert!(le_slice_to_string(not_utf16le).is_err()); + } + + #[test] + fn test_utf7_string_abc() { + let abc = &[0x41, 0x42, 0x43, 0x00, 0x00]; + assert_eq!(String::from("ABC"), utf7_slice_to_string(abc).unwrap()); + } + + #[test] + fn test_utf7_string_empty() { + let empty = &[]; + assert_eq!(String::from(""), utf7_slice_to_string(empty).unwrap()); + } + + #[test] + fn test_utf7_string_invalid() { + let not_utf7 = &[0x80]; + assert!(utf7_slice_to_string(not_utf7).is_err()); + } + + #[test] + fn test_length_single_length() { + let bytes = &[0x28]; + assert_eq!(Ok((&[][..], 0x28)), parse_per_length_determinant(bytes)); + } + + #[test] + fn test_length_double_length() { + let bytes = &[0x81, 0x28]; + assert_eq!(Ok((&[][..], 0x128)), parse_per_length_determinant(bytes)); + } + + #[test] + fn test_length_single_length_incomplete() { + let bytes = &[]; + assert_eq!( + Err(Err::Incomplete(Needed::new(1))), + parse_per_length_determinant(bytes) + ) + } + + #[test] + fn test_length_16k_unimplemented() { + let bytes = &[0xc0]; + assert_eq!( + Err(Err::Error(RdpError::UnimplementedLengthDeterminant)), + parse_per_length_determinant(bytes) + ) + } + + #[test] + fn test_length_double_length_incomplete() { + let bytes = &[0x81]; + assert_eq!( + Err(Err::Incomplete(Needed::new(2))), + parse_per_length_determinant(bytes) + ) + } +} diff --git a/rust/src/rdp/windows.rs b/rust/src/rdp/windows.rs new file mode 100644 index 0000000..29ee4b2 --- /dev/null +++ b/rust/src/rdp/windows.rs @@ -0,0 +1,660 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Zach Kelly <zach.kelly@lmco.com> + +/// converts a locale identifier into a locale name +/// <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f> +pub fn lcid_to_string(lcid: u32, default: &str) -> String { + let s = match lcid { + 0x0001 => "ar", + 0x0002 => "bg", + 0x0003 => "ca", + 0x0004 => "zh-Hans", + 0x0005 => "cs", + 0x0006 => "da", + 0x0007 => "de", + 0x0008 => "el", + 0x0009 => "en", + 0x000A => "es", + 0x000B => "fi", + 0x000C => "fr", + 0x000D => "he", + 0x000E => "hu", + 0x000F => "is", + 0x0010 => "it", + 0x0011 => "ja", + 0x0012 => "ko", + 0x0013 => "nl", + 0x0014 => "no", + 0x0015 => "pl", + 0x0016 => "pt", + 0x0017 => "rm", + 0x0018 => "ro", + 0x0019 => "ru", + 0x001A => "hr", + 0x001B => "sk", + 0x001C => "sq", + 0x001D => "sv", + 0x001E => "th", + 0x001F => "tr", + 0x0020 => "ur", + 0x0021 => "id", + 0x0022 => "uk", + 0x0023 => "be", + 0x0024 => "sl", + 0x0025 => "et", + 0x0026 => "lv", + 0x0027 => "lt", + 0x0028 => "tg", + 0x0029 => "fa", + 0x002A => "vi", + 0x002B => "hy", + 0x002C => "az", + 0x002D => "eu", + 0x002E => "hsb", + 0x002F => "mk", + 0x0030 => "st", + 0x0031 => "ts", + 0x0032 => "tn", + 0x0033 => "ve", + 0x0034 => "xh", + 0x0035 => "zu", + 0x0036 => "af", + 0x0037 => "ka", + 0x0038 => "fo", + 0x0039 => "hi", + 0x003A => "mt", + 0x003B => "se", + 0x003C => "ga", + 0x003D => "yi", + 0x003E => "ms", + 0x003F => "kk", + 0x0040 => "ky", + 0x0041 => "sw", + 0x0042 => "tk", + 0x0043 => "uz", + 0x0044 => "tt", + 0x0045 => "bn", + 0x0046 => "pa", + 0x0047 => "gu", + 0x0048 => "or", + 0x0049 => "ta", + 0x004A => "te", + 0x004B => "kn", + 0x004C => "ml", + 0x004D => "as", + 0x004E => "mr", + 0x004F => "sa", + 0x0050 => "mn", + 0x0051 => "bo", + 0x0052 => "cy", + 0x0053 => "km", + 0x0054 => "lo", + 0x0055 => "my", + 0x0056 => "gl", + 0x0057 => "kok", + 0x0058 => "mni", + 0x0059 => "sd", + 0x005A => "syr", + 0x005B => "si", + 0x005C => "chr", + 0x005D => "iu", + 0x005E => "am", + 0x005F => "tzm", + 0x0060 => "ks", + 0x0061 => "ne", + 0x0062 => "fy", + 0x0063 => "ps", + 0x0064 => "fil", + 0x0065 => "dv", + 0x0066 => "bin", + 0x0067 => "ff", + 0x0068 => "ha", + 0x0069 => "ibb", + 0x006A => "yo", + 0x006B => "quz", + 0x006C => "nso", + 0x006D => "ba", + 0x006E => "lb", + 0x006F => "kl", + 0x0070 => "ig", + 0x0071 => "kr", + 0x0072 => "om", + 0x0073 => "ti", + 0x0074 => "gn", + 0x0075 => "haw", + 0x0076 => "la", + 0x0077 => "so", + 0x0078 => "ii", + 0x0079 => "pap", + 0x007A => "arn", + 0x007C => "moh", + 0x007E => "br", + 0x0080 => "ug", + 0x0081 => "mi", + 0x0082 => "oc", + 0x0083 => "co", + 0x0084 => "gsw", + 0x0085 => "sah", + 0x0086 => "qut", + 0x0087 => "rw", + 0x0088 => "wo", + 0x008C => "prs", + 0x0091 => "gd", + 0x0092 => "ku", + 0x0093 => "quc", + 0x0401 => "ar-SA", + 0x0402 => "bg-BG", + 0x0403 => "ca-ES", + 0x0404 => "zh-TW", + 0x0405 => "cs-CZ", + 0x0406 => "da-DK", + 0x0407 => "de-DE", + 0x0408 => "el-GR", + 0x0409 => "en-US", + 0x040A => "es-ES_tradnl", + 0x040B => "fi-FI", + 0x040C => "fr-FR", + 0x040D => "he-IL", + 0x040E => "hu-HU", + 0x040F => "is-IS", + 0x0410 => "it-IT", + 0x0411 => "ja-JP", + 0x0412 => "ko-KR", + 0x0413 => "nl-NL", + 0x0414 => "nb-NO", + 0x0415 => "pl-PL", + 0x0416 => "pt-BR", + 0x0417 => "rm-CH", + 0x0418 => "ro-RO", + 0x0419 => "ru-RU", + 0x041A => "hr-HR", + 0x041B => "sk-SK", + 0x041C => "sq-AL", + 0x041D => "sv-SE", + 0x041E => "th-TH", + 0x041F => "tr-TR", + 0x0420 => "ur-PK", + 0x0421 => "id-ID", + 0x0422 => "uk-UA", + 0x0423 => "be-BY", + 0x0424 => "sl-SI", + 0x0425 => "et-EE", + 0x0426 => "lv-LV", + 0x0427 => "lt-LT", + 0x0428 => "tg-Cyrl-TJ", + 0x0429 => "fa-IR", + 0x042A => "vi-VN", + 0x042B => "hy-AM", + 0x042C => "az-Latn-AZ", + 0x042D => "eu-ES", + 0x042E => "hsb-DE", + 0x042F => "mk-MK", + 0x0430 => "st-ZA", + 0x0431 => "ts-ZA", + 0x0432 => "tn-ZA", + 0x0433 => "ve-ZA", + 0x0434 => "xh-ZA", + 0x0435 => "zu-ZA", + 0x0436 => "af-ZA", + 0x0437 => "ka-GE", + 0x0438 => "fo-FO", + 0x0439 => "hi-IN", + 0x043A => "mt-MT", + 0x043B => "se-NO", + 0x043D => "yi-Hebr", + 0x043E => "ms-MY", + 0x043F => "kk-KZ", + 0x0440 => "ky-KG", + 0x0441 => "sw-KE", + 0x0442 => "tk-TM", + 0x0443 => "uz-Latn-UZ", + 0x0444 => "tt-RU", + 0x0445 => "bn-IN", + 0x0446 => "pa-IN", + 0x0447 => "gu-IN", + 0x0448 => "or-IN", + 0x0449 => "ta-IN", + 0x044A => "te-IN", + 0x044B => "kn-IN", + 0x044C => "ml-IN", + 0x044D => "as-IN", + 0x044E => "mr-IN", + 0x044F => "sa-IN", + 0x0450 => "mn-MN", + 0x0451 => "bo-CN", + 0x0452 => "cy-GB", + 0x0453 => "km-KH", + 0x0454 => "lo-LA", + 0x0455 => "my-MM", + 0x0456 => "gl-ES", + 0x0457 => "kok-IN", + 0x0458 => "mni-IN", + 0x0459 => "sd-Deva-IN", + 0x045A => "syr-SY", + 0x045B => "si-LK", + 0x045C => "chr-Cher-US", + 0x045D => "iu-Cans-CA", + 0x045E => "am-ET", + 0x045F => "tzm-Arab-MA", + 0x0460 => "ks-Arab", + 0x0461 => "ne-NP", + 0x0462 => "fy-NL", + 0x0463 => "ps-AF", + 0x0464 => "fil-PH", + 0x0465 => "dv-MV", + 0x0466 => "bin-NG", + 0x0467 => "fuv-NG", + 0x0468 => "ha-Latn-NG", + 0x0469 => "ibb-NG", + 0x046A => "yo-NG", + 0x046B => "quz-BO", + 0x046C => "nso-ZA", + 0x046D => "ba-RU", + 0x046E => "lb-LU", + 0x046F => "kl-GL", + 0x0470 => "ig-NG", + 0x0471 => "kr-NG", + 0x0472 => "om-ET", + 0x0473 => "ti-ET", + 0x0474 => "gn-PY", + 0x0475 => "haw-US", + 0x0476 => "la-Latn", + 0x0477 => "so-SO", + 0x0478 => "ii-CN", + 0x0479 => "pap-029", + 0x047A => "arn-CL", + 0x047C => "moh-CA", + 0x047E => "br-FR", + 0x0480 => "ug-CN", + 0x0481 => "mi-NZ", + 0x0482 => "oc-FR", + 0x0483 => "co-FR", + 0x0484 => "gsw-FR", + 0x0485 => "sah-RU", + 0x0486 => "qut-GT", + 0x0487 => "rw-RW", + 0x0488 => "wo-SN", + 0x048C => "prs-AF", + 0x048D => "plt-MG", + 0x048E => "zh-yue-HK", + 0x048F => "tdd-Tale-CN", + 0x0490 => "khb-Talu-CN", + 0x0491 => "gd-GB", + 0x0492 => "ku-Arab-IQ", + 0x0493 => "quc-CO", + 0x0501 => "qps-ploc", + 0x05FE => "qps-ploca", + 0x0801 => "ar-IQ", + 0x0803 => "ca-ES-valencia", + 0x0804 => "zh-CN", + 0x0807 => "de-CH", + 0x0809 => "en-GB", + 0x080A => "es-MX", + 0x080C => "fr-BE", + 0x0810 => "it-CH", + 0x0811 => "ja-Ploc-JP", + 0x0813 => "nl-BE", + 0x0814 => "nn-NO", + 0x0816 => "pt-PT", + 0x0818 => "ro-MD", + 0x0819 => "ru-MD", + 0x081A => "sr-Latn-CS", + 0x081D => "sv-FI", + 0x0820 => "ur-IN", + 0x082C => "az-Cyrl-AZ", + 0x082E => "dsb-DE", + 0x0832 => "tn-BW", + 0x083B => "se-SE", + 0x083C => "ga-IE", + 0x083E => "ms-BN", + 0x0843 => "uz-Cyrl-UZ", + 0x0845 => "bn-BD", + 0x0846 => "pa-Arab-PK", + 0x0849 => "ta-LK", + 0x0850 => "mn-Mong-CN", + 0x0851 => "bo-BT", + 0x0859 => "sd-Arab-PK", + 0x085D => "iu-Latn-CA", + 0x085F => "tzm-Latn-DZ", + 0x0860 => "ks-Deva", + 0x0861 => "ne-IN", + 0x0867 => "ff-Latn-SN", + 0x086B => "quz-EC", + 0x0873 => "ti-ER", + 0x09FF => "qps-plocm", + 0x0C01 => "ar-EG", + 0x0C04 => "zh-HK", + 0x0C07 => "de-AT", + 0x0C09 => "en-AU", + 0x0C0A => "es-ES", + 0x0C0C => "fr-CA", + 0x0C1A => "sr-Cyrl-CS", + 0x0C3B => "se-FI", + 0x0C50 => "mn-Mong-MN", + 0x0C51 => "dz-BT", + 0x0C5F => "tmz-MA", + 0x0C6B => "quz-PE", + 0x1001 => "ar-LY", + 0x1004 => "zh-SG", + 0x1007 => "de-LU", + 0x1009 => "en-CA", + 0x100A => "es-GT", + 0x100C => "fr-CH", + 0x101A => "hr-BA", + 0x103B => "smj-NO", + 0x105F => "tzm-Tfng-MA", + 0x1401 => "ar-DZ", + 0x1404 => "zh-MO", + 0x1407 => "de-LI", + 0x1409 => "en-NZ", + 0x140A => "es-CR", + 0x140C => "fr-LU", + 0x141A => "bs-Latn-BA", + 0x143B => "smj-SE", + 0x1801 => "ar-MA", + 0x1809 => "en-IE", + 0x180A => "es-PA", + 0x180C => "fr-MC", + 0x181A => "sr-Latn-BA", + 0x183B => "sma-NO", + 0x1C01 => "ar-TN", + 0x1C09 => "en-ZA", + 0x1C0A => "es-DO", + 0x1C1A => "sr-Cyrl-BA", + 0x1C3B => "sma-SE", + 0x2001 => "ar-OM", + 0x2009 => "en-JM", + 0x200A => "es-VE", + 0x200C => "fr-RE", + 0x201A => "bs-Cyrl-BA", + 0x203B => "sms-FI", + 0x2401 => "ar-YE", + 0x2409 => "en-029", + 0x240A => "es-CO", + 0x240C => "fr-CD", + 0x241A => "sr-Latn-RS", + 0x243B => "smn-FI", + 0x2801 => "ar-SY", + 0x2809 => "en-BZ", + 0x280A => "es-PE", + 0x280C => "fr-SN", + 0x281A => "sr-Cyrl-RS", + 0x2C01 => "ar-JO", + 0x2C09 => "en-TT", + 0x2C0A => "es-AR", + 0x2C0C => "fr-CM", + 0x2C1A => "sr-Latn-ME", + 0x3001 => "ar-LB", + 0x3009 => "en-ZW", + 0x300A => "es-EC", + 0x300C => "fr-CI", + 0x301A => "sr-Cyrl-ME", + 0x3401 => "ar-KW", + 0x3409 => "en-PH", + 0x340A => "es-CL", + 0x340C => "fr-ML", + 0x3801 => "ar-AE", + 0x3809 => "en-ID", + 0x380A => "es-UY", + 0x380C => "fr-MA", + 0x3c01 => "ar-BH", + 0x3c09 => "en-HK", + 0x3C0A => "es-PY", + 0x3C0C => "fr-HT", + 0x4001 => "ar-QA", + 0x4009 => "en-IN", + 0x400A => "es-BO", + 0x4401 => "ar-Ploc-SA", + 0x4409 => "en-MY", + 0x440A => "es-SV", + 0x4801 => "ar-145", + 0x4809 => "en-SG", + 0x480A => "es-HN", + 0x4C09 => "en-AE", + 0x4C0A => "es-NI", + 0x5009 => "en-BH", + 0x500A => "es-PR", + 0x5409 => "en-EG", + 0x540A => "es-US", + 0x5809 => "en-JO", + 0x580A => "es-419", + 0x5C09 => "en-KW", + 0x5C0A => "es-CU", + 0x6009 => "en-TR", + 0x6409 => "en-YE", + 0x641A => "bs-Cyrl", + 0x681A => "bs-Latn", + 0x6C1A => "sr-Cyrl", + 0x701A => "sr-Latn", + 0x703B => "smn", + 0x742C => "az-Cyrl", + 0x743B => "sms", + 0x7804 => "zh", + 0x7814 => "nn", + 0x781A => "bs", + 0x782C => "az-Latn", + 0x783B => "sma", + 0x7843 => "uz-Cyrl", + 0x7850 => "mn-Cyrl", + 0x785D => "iu-Cans", + 0x785F => "tzm-Tfng", + 0x7C04 => "zh-Hant", + 0x7C14 => "nb", + 0x7C1A => "sr", + 0x7C28 => "tg-Cyrl", + 0x7C2E => "dsb", + 0x7C3B => "smj", + 0x7C43 => "uz-Latn", + 0x7C46 => "pa-Arab", + 0x7C50 => "mn-Mong", + 0x7C59 => "sd-Arab", + 0x7C5C => "chr-Cher", + 0x7C5D => "iu-Latn", + 0x7C5F => "tzm-Latn", + 0x7C67 => "ff-Latn", + 0x7C68 => "ha-Latn", + 0x7C92 => "ku-Arab", + _ => default, + }; + String::from(s) +} + +/// Windows operating system type (build and suffix/pack) +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OperatingSystem { + pub build: Build, + pub suffix: Suffix, +} + +// <https://en.wikipedia.org/wiki/Windows_NT#Releases> +#[derive(Clone, Debug, FromPrimitive, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum Build { + Other, + Win31 = 528, + Win35 = 807, + Win351 = 1057, + Win40 = 1381, + Win2000 = 2195, + WinXP = 2600, + Vista_6000 = 6000, + Vista_6001 = 6001, + Vista_6002 = 6002, + Win7_7600 = 7600, + Win7_7601 = 7601, + Win8 = 9200, + Win81 = 9600, + Win10_10240 = 10240, + Win10_10586 = 10586, + Win10_14393 = 14393, + Win10_15063 = 15063, + Win10_16299 = 16299, + Win10_17134 = 17134, + Win10_17763 = 17763, + Server2003 = 3790, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Suffix { + Empty, + Rtm, + Sp1, + Sp2, + Th1, + Th2, + Rs1, + Rs2, + Rs3, + Rs4, + Rs5, +} + +/// convert a build number into an OperatingSystem type +pub fn build_number_to_os(number: u32) -> OperatingSystem { + let build = match num::FromPrimitive::from_u32(number) { + Some(x) => x, + None => Build::Other, + }; + let suffix = match number { + 6000 => Suffix::Rtm, + 7600 => Suffix::Rtm, + 6001 => Suffix::Sp1, + 6002 => Suffix::Sp2, + 7601 => Suffix::Sp1, + 10240 => Suffix::Th1, + 10586 => Suffix::Th2, + 14393 => Suffix::Rs1, + 15063 => Suffix::Rs2, + 16299 => Suffix::Rs3, + 17134 => Suffix::Rs4, + 17763 => Suffix::Rs5, + _ => Suffix::Empty, + }; + OperatingSystem { build, suffix } +} + +/// convert an OperatingSystem into a string description +pub fn os_to_string(os: &OperatingSystem, default: &str) -> String { + let s = match os.build { + Build::Win31 => "Windows NT 3.1", + Build::Win35 => "Windows NT 3.5", + Build::Win351 => "Windows NT 3.51", + Build::Win40 => "Windows NT 4.0", + Build::Win2000 => "Windows 2000", + Build::WinXP => "Windows XP", + Build::Vista_6000 => "Windows Vista", + Build::Vista_6001 => "Windows Vista", + Build::Vista_6002 => "Windows Vista", + Build::Win7_7600 => "Windows 7", + Build::Win7_7601 => "Windows 7", + Build::Win8 => "Windows 8", + Build::Win81 => "Windows 8.1", + Build::Win10_10240 => "Windows 10", + Build::Win10_10586 => "Windows 10", + Build::Win10_14393 => "Windows 10", + Build::Win10_15063 => "Windows 10", + Build::Win10_16299 => "Windows 10", + Build::Win10_17134 => "Windows 10", + Build::Win10_17763 => "Windows 10", + Build::Server2003 => "Windows Server 2003", + Build::Other => default, + }; + let mut result = String::from(s); + match os.suffix { + Suffix::Rtm => result.push_str(" RTM"), + Suffix::Sp1 => result.push_str(" SP1"), + Suffix::Sp2 => result.push_str(" SP2"), + Suffix::Th1 => result.push_str(" TH1"), + Suffix::Th2 => result.push_str(" TH2"), + Suffix::Rs1 => result.push_str(" RS1"), + Suffix::Rs2 => result.push_str(" RS2"), + Suffix::Rs3 => result.push_str(" RS3"), + Suffix::Rs4 => result.push_str(" RS4"), + Suffix::Rs5 => result.push_str(" RS5"), + Suffix::Empty => (), + }; + result +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_lcid_string_en() { + let default = "default-lcid-name"; + assert_eq!("en-US", lcid_to_string(0x409, default)); + } + + #[test] + fn test_lcid_string_default() { + let default = "default-lcid-name"; + assert_eq!(default, lcid_to_string(0xffff, default)); + } + + #[test] + fn test_build_os_win10() { + let w10_rs5 = OperatingSystem { + build: Build::Win10_17763, + suffix: Suffix::Rs5, + }; + assert_eq!(w10_rs5, build_number_to_os(17763)); + } + + #[test] + fn test_build_os_other() { + let other = OperatingSystem { + build: Build::Other, + suffix: Suffix::Empty, + }; + assert_eq!(other, build_number_to_os(1)); + } + + #[test] + fn test_os_string_win7_sp1() { + let w7_sp1 = "Windows 7 SP1"; + let default = "default-os-name"; + let w7_os = OperatingSystem { + build: Build::Win7_7601, + suffix: Suffix::Sp1, + }; + assert_eq!(w7_sp1, os_to_string(&w7_os, default)); + } + + #[test] + fn test_os_string_win81() { + let w81 = "Windows 8.1"; + let default = "default-os-name"; + let w81_os = OperatingSystem { + build: Build::Win81, + suffix: Suffix::Empty, + }; + assert_eq!(w81, os_to_string(&w81_os, default)); + } + + #[test] + fn test_os_string_default() { + let default = "default-os-name"; + let other_os = OperatingSystem { + build: Build::Other, + suffix: Suffix::Empty, + }; + assert_eq!(default, os_to_string(&other_os, default)); + } +} diff --git a/rust/src/rfb/detect.rs b/rust/src/rfb/detect.rs new file mode 100644 index 0000000..1cf0887 --- /dev/null +++ b/rust/src/rfb/detect.rs @@ -0,0 +1,64 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +use crate::rfb::rfb::*; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_name( + tx: &mut RFBTransaction, buffer: *mut *const u8, buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.tc_server_init { + let p = &r.name; + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_sectype(tx: &mut RFBTransaction, sectype: *mut u32) -> u8 { + if let Some(ref r) = tx.chosen_security_type { + *sectype = *r; + return 1; + } + + *sectype = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_secresult( + tx: &mut RFBTransaction, secresult: *mut u32, +) -> u8 { + if let Some(ref r) = tx.tc_security_result { + *secresult = r.status; + return 1; + } + + return 0; +} diff --git a/rust/src/rfb/logger.rs b/rust/src/rfb/logger.rs new file mode 100644 index 0000000..62bb209 --- /dev/null +++ b/rust/src/rfb/logger.rs @@ -0,0 +1,135 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +use super::rfb::RFBTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use std; +use std::fmt::Write; + +fn log_rfb(tx: &RFBTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("rfb")?; + + // Protocol version + if let Some(tx_spv) = &tx.tc_server_protocol_version { + js.open_object("server_protocol_version")?; + js.set_string("major", &tx_spv.major)?; + js.set_string("minor", &tx_spv.minor)?; + js.close()?; + } + if let Some(tx_cpv) = &tx.ts_client_protocol_version { + js.open_object("client_protocol_version")?; + js.set_string("major", &tx_cpv.major)?; + js.set_string("minor", &tx_cpv.minor)?; + js.close()?; + } + + // Authentication + js.open_object("authentication")?; + if let Some(chosen_security_type) = tx.chosen_security_type { + js.set_uint("security_type", chosen_security_type as u64)?; + } + #[allow(clippy::single_match)] + match tx.chosen_security_type { + Some(2) => { + js.open_object("vnc")?; + if let Some(ref sc) = tx.tc_vnc_challenge { + let mut s = String::new(); + for &byte in &sc.secret[..] { + write!(&mut s, "{:02x}", byte).expect("Unable to write"); + } + js.set_string("challenge", &s)?; + } + if let Some(ref sr) = tx.ts_vnc_response { + let mut s = String::new(); + for &byte in &sr.secret[..] { + write!(&mut s, "{:02x}", byte).expect("Unable to write"); + } + js.set_string("response", &s)?; + } + js.close()?; + } + _ => (), + } + if let Some(security_result) = &tx.tc_security_result { + let _ = match security_result.status { + 0 => js.set_string("security_result", "OK")?, + 1 => js.set_string("security-result", "FAIL")?, + 2 => js.set_string("security_result", "TOOMANY")?, + _ => js.set_string( + "security_result", + &format!("UNKNOWN ({})", security_result.status), + )?, + }; + } + js.close()?; // Close authentication. + + if let Some(ref reason) = tx.tc_failure_reason { + js.set_string("server_security_failure_reason", &reason.reason_string)?; + } + + // Client/Server init + if let Some(s) = &tx.ts_client_init { + js.set_bool("screen_shared", s.shared != 0)?; + } + if let Some(tc_server_init) = &tx.tc_server_init { + js.open_object("framebuffer")?; + js.set_uint("width", tc_server_init.width as u64)?; + js.set_uint("height", tc_server_init.height as u64)?; + js.set_string_from_bytes("name", &tc_server_init.name)?; + + js.open_object("pixel_format")?; + js.set_uint( + "bits_per_pixel", + tc_server_init.pixel_format.bits_per_pixel as u64, + )?; + js.set_uint("depth", tc_server_init.pixel_format.depth as u64)?; + js.set_bool( + "big_endian", + tc_server_init.pixel_format.big_endian_flag != 0, + )?; + js.set_bool( + "true_color", + tc_server_init.pixel_format.true_colour_flag != 0, + )?; + js.set_uint("red_max", tc_server_init.pixel_format.red_max as u64)?; + js.set_uint("green_max", tc_server_init.pixel_format.green_max as u64)?; + js.set_uint("blue_max", tc_server_init.pixel_format.blue_max as u64)?; + js.set_uint("red_shift", tc_server_init.pixel_format.red_shift as u64)?; + js.set_uint( + "green_shift", + tc_server_init.pixel_format.green_shift as u64, + )?; + js.set_uint("blue_shift", tc_server_init.pixel_format.blue_shift as u64)?; + js.close()?; + + js.close()?; + } + + js.close()?; + + return Ok(()); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_logger_log( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, RFBTransaction); + log_rfb(tx, js).is_ok() +} diff --git a/rust/src/rfb/mod.rs b/rust/src/rfb/mod.rs new file mode 100644 index 0000000..050ee77 --- /dev/null +++ b/rust/src/rfb/mod.rs @@ -0,0 +1,25 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! RFB protocol parser, logger and detection module. + +// Author: Frank Honza <frank.honza@dcso.de> + +pub mod detect; +pub mod logger; +pub mod parser; +pub mod rfb; diff --git a/rust/src/rfb/parser.rs b/rust/src/rfb/parser.rs new file mode 100644 index 0000000..7395806 --- /dev/null +++ b/rust/src/rfb/parser.rs @@ -0,0 +1,398 @@ +/* Copyright (C) 2020-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Frank Honza <frank.honza@dcso.de> + +use nom7::bytes::streaming::tag; +use nom7::bytes::streaming::take; +use nom7::combinator::map_res; +use nom7::number::streaming::*; +use nom7::*; +use std::fmt; +use std::str; + +#[derive(Debug, PartialEq)] +pub enum RFBGlobalState { + TCServerProtocolVersion, + TCSupportedSecurityTypes, + TCVncChallenge, + TCServerInit, + TCFailureReason, + TSClientProtocolVersion, + TCServerSecurityType, + TSSecurityTypeSelection, + TSVncResponse, + TCSecurityResult, + TSClientInit, + Skip, +} + +impl fmt::Display for RFBGlobalState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + RFBGlobalState::TCServerProtocolVersion => write!(f, "TCServerProtocolVersion"), + RFBGlobalState::TCSupportedSecurityTypes => write!(f, "TCSupportedSecurityTypes"), + RFBGlobalState::TCVncChallenge => write!(f, "TCVncChallenge"), + RFBGlobalState::TCServerInit => write!(f, "TCServerInit"), + RFBGlobalState::TCFailureReason => write!(f, "TCFailureReason"), + RFBGlobalState::TSClientProtocolVersion => write!(f, "TSClientProtocolVersion"), + RFBGlobalState::TSSecurityTypeSelection => write!(f, "TSSecurityTypeSelection"), + RFBGlobalState::TSVncResponse => write!(f, "TSVncResponse"), + RFBGlobalState::TCSecurityResult => write!(f, "TCSecurityResult"), + RFBGlobalState::TCServerSecurityType => write!(f, "TCServerSecurityType"), + RFBGlobalState::TSClientInit => write!(f, "TSClientInit"), + RFBGlobalState::Skip => write!(f, "Skip"), + } + } +} + +pub struct ProtocolVersion { + pub major: String, + pub minor: String, +} + +pub struct SupportedSecurityTypes { + pub number_of_types: u8, + pub types: Vec<u8>, +} + +pub struct SecurityTypeSelection { + pub security_type: u8, +} + +pub struct ServerSecurityType { + pub security_type: u32, +} + +pub struct SecurityResult { + pub status: u32, +} + +pub struct FailureReason { + pub reason_string: String, +} + +pub struct VncAuth { + pub secret: Vec<u8>, +} + +pub struct ClientInit { + pub shared: u8, +} + +pub struct PixelFormat { + pub bits_per_pixel: u8, + pub depth: u8, + pub big_endian_flag: u8, + pub true_colour_flag: u8, + pub red_max: u16, + pub green_max: u16, + pub blue_max: u16, + pub red_shift: u8, + pub green_shift: u8, + pub blue_shift: u8, +} + +pub struct ServerInit { + pub width: u16, + pub height: u16, + pub pixel_format: PixelFormat, + pub name_length: u32, + pub name: Vec<u8>, +} + +pub fn parse_protocol_version(i: &[u8]) -> IResult<&[u8], ProtocolVersion> { + let (i, _) = tag("RFB ")(i)?; + let (i, major) = map_res(take(3_usize), str::from_utf8)(i)?; + let (i, _) = tag(".")(i)?; + let (i, minor) = map_res(take(3_usize), str::from_utf8)(i)?; + let (i, _) = tag("\n")(i)?; + Ok(( + i, + ProtocolVersion { + major: major.to_string(), + minor: minor.to_string(), + }, + )) +} + +pub fn parse_supported_security_types(i: &[u8]) -> IResult<&[u8], SupportedSecurityTypes> { + let (i, number_of_types) = be_u8(i)?; + let (i, types) = take(number_of_types as usize)(i)?; + Ok(( + i, + SupportedSecurityTypes { + number_of_types, + types: types.to_vec(), + }, + )) +} + +pub fn parse_server_security_type(i: &[u8]) -> IResult<&[u8], ServerSecurityType> { + let (i, security_type) = be_u32(i)?; + Ok((i, ServerSecurityType { security_type })) +} + +pub fn parse_vnc_auth(i: &[u8]) -> IResult<&[u8], VncAuth> { + let (i, secret) = take(16_usize)(i)?; + Ok(( + i, + VncAuth { + secret: secret.to_vec(), + }, + )) +} + +pub fn parse_security_type_selection(i: &[u8]) -> IResult<&[u8], SecurityTypeSelection> { + let (i, security_type) = be_u8(i)?; + Ok((i, SecurityTypeSelection { security_type })) +} + +pub fn parse_security_result(i: &[u8]) -> IResult<&[u8], SecurityResult> { + let (i, status) = be_u32(i)?; + Ok((i, SecurityResult { status })) +} + +pub fn parse_failure_reason(i: &[u8]) -> IResult<&[u8], FailureReason> { + let (i, reason_length) = be_u32(i)?; + let (i, reason_string) = map_res(take(reason_length as usize), str::from_utf8)(i)?; + Ok(( + i, + FailureReason { + reason_string: reason_string.to_string(), + }, + )) +} + +pub fn parse_client_init(i: &[u8]) -> IResult<&[u8], ClientInit> { + let (i, shared) = be_u8(i)?; + Ok((i, ClientInit { shared })) +} + +pub fn parse_pixel_format(i: &[u8]) -> IResult<&[u8], PixelFormat> { + let (i, bits_per_pixel) = be_u8(i)?; + let (i, depth) = be_u8(i)?; + let (i, big_endian_flag) = be_u8(i)?; + let (i, true_colour_flag) = be_u8(i)?; + let (i, red_max) = be_u16(i)?; + let (i, green_max) = be_u16(i)?; + let (i, blue_max) = be_u16(i)?; + let (i, red_shift) = be_u8(i)?; + let (i, green_shift) = be_u8(i)?; + let (i, blue_shift) = be_u8(i)?; + let (i, _) = take(3_usize)(i)?; + let format = PixelFormat { + bits_per_pixel, + depth, + big_endian_flag, + true_colour_flag, + red_max, + green_max, + blue_max, + red_shift, + green_shift, + blue_shift, + }; + Ok((i, format)) +} + +pub fn parse_server_init(i: &[u8]) -> IResult<&[u8], ServerInit> { + let (i, width) = be_u16(i)?; + let (i, height) = be_u16(i)?; + let (i, pixel_format) = parse_pixel_format(i)?; + let (i, name_length) = be_u32(i)?; + let (i, name) = take(name_length as usize)(i)?; + let init = ServerInit { + width, + height, + pixel_format, + name_length, + name: name.to_vec(), + }; + Ok((i, init)) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Simple test of some valid data. + #[test] + fn test_parse_version() { + let buf = b"RFB 003.008\n"; + + let result = parse_protocol_version(buf); + match result { + Ok((remainder, message)) => { + // Check the first message. + assert_eq!(message.major, "003"); + + // And we should have 0 bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_server_init() { + let buf = [ + 0x05, 0x00, 0x03, 0x20, 0x20, 0x18, 0x00, 0x01, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, + 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x61, 0x6e, 0x65, 0x61, + 0x67, 0x6c, 0x65, 0x73, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + ]; + + let result = parse_server_init(&buf); + match result { + Ok((remainder, message)) => { + // Check the first message. + assert_eq!(message.width, 1280); + assert_eq!(message.height, 800); + assert_eq!(message.pixel_format.bits_per_pixel, 32); + + // And we should have 0 bytes left. + assert_eq!(remainder.len(), 0); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_pixel_format() { + let buf = [ + 0x20, /* Bits per pixel: 32 */ + 0x18, /* Depth: 24 */ + 0x00, /* Big endian flag: False */ + 0x01, /* True color flag: True */ + 0x00, 0xff, /* Red maximum: 255 */ + 0x00, 0xff, /* Green maximum: 255 */ + 0x00, 0xff, /* Blue maximum: 255 */ + 0x10, /* Red shift: 16 */ + 0x08, /* Green shift: 8 */ + 0x00, /* Blue shift: 0 */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa0, + ]; + + let result = parse_pixel_format(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message.bits_per_pixel, 32); + assert_eq!(message.depth, 24); + assert_eq!(message.big_endian_flag, 0); + assert_eq!(message.true_colour_flag, 1); + assert_eq!(message.red_max, 255); + assert_eq!(message.green_max, 255); + assert_eq!(message.blue_max, 255); + assert_eq!(message.red_shift, 16); + assert_eq!(message.green_shift, 8); + assert_eq!(message.blue_shift, 0); + assert_eq!(remainder.len(), 5); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_supported_security_types() { + let buf = [ + 0x01, /* Number of security types: 1 */ + 0x02, /* Security type: VNC (2) */ + 0x00, 0x01, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0xa0, + ]; + + let result = parse_supported_security_types(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message.number_of_types, 1); + assert_eq!(message.types[0], 2); + assert_eq!(remainder.len(), 19); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_vnc_auth() { + let buf = [ + 0x54, 0x7b, 0x7a, 0x6f, 0x36, 0xa1, 0x54, 0xdb, 0x03, 0xa2, 0x57, 0x5c, 0x6f, 0x2a, + 0x4e, 0xc5, /* Authentication challenge: 547b7a6f36a154db03a2575c6f2a4ec5 */ + 0x00, 0x00, 0x00, 0x01, 0xa0, + ]; + + let result = parse_vnc_auth(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!( + hex::encode(message.secret), + "547b7a6f36a154db03a2575c6f2a4ec5" + ); + assert_eq!(remainder.len(), 5); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_client_init() { + let buf = [ + 0x00, /*Share desktop flag: False*/ + 0x7b, 0x7a, 0x6f, 0x36, 0xa1, 0x54, 0xdb, 0x03, 0xa2, 0x57, 0x5c, 0x6f, 0x2a, 0x4e, + 0xc5, 0x00, 0x00, 0x00, 0x01, 0xa0, + ]; + + let result = parse_client_init(&buf); + match result { + Ok((remainder, message)) => { + assert_eq!(message.shared, 0); + assert_eq!(remainder.len(), 20); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + Err(Err::Error(err)) | Err(Err::Failure(err)) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } +} diff --git a/rust/src/rfb/rfb.rs b/rust/src/rfb/rfb.rs new file mode 100644 index 0000000..8c33813 --- /dev/null +++ b/rust/src/rfb/rfb.rs @@ -0,0 +1,995 @@ +/* Copyright (C) 2020-2023 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. + */ + +// Author: Frank Honza <frank.honza@dcso.de> +// Sascha Steinbiss <sascha.steinbiss@dcso.de> + +use super::parser; +use crate::applayer; +use crate::applayer::*; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP}; +use crate::frames::*; +use nom7::Err; +use std; +use std::ffi::CString; + +static mut ALPROTO_RFB: AppProto = ALPROTO_UNKNOWN; + +#[derive(FromPrimitive, Debug, AppLayerEvent)] +pub enum RFBEvent { + UnimplementedSecurityType, + UnknownSecurityResult, + MalformedMessage, + ConfusedState, +} + +#[derive(AppLayerFrameType)] +pub enum RFBFrameType { + Pdu, +} +pub struct RFBTransaction { + tx_id: u64, + pub complete: bool, + pub chosen_security_type: Option<u32>, + + pub tc_server_protocol_version: Option<parser::ProtocolVersion>, + pub ts_client_protocol_version: Option<parser::ProtocolVersion>, + pub tc_supported_security_types: Option<parser::SupportedSecurityTypes>, + pub ts_security_type_selection: Option<parser::SecurityTypeSelection>, + pub tc_server_security_type: Option<parser::ServerSecurityType>, + pub tc_vnc_challenge: Option<parser::VncAuth>, + pub ts_vnc_response: Option<parser::VncAuth>, + pub ts_client_init: Option<parser::ClientInit>, + pub tc_security_result: Option<parser::SecurityResult>, + pub tc_failure_reason: Option<parser::FailureReason>, + pub tc_server_init: Option<parser::ServerInit>, + + tx_data: applayer::AppLayerTxData, +} + +impl Transaction for RFBTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +impl Default for RFBTransaction { + fn default() -> Self { + Self::new() + } +} + +impl RFBTransaction { + pub fn new() -> Self { + Self { + tx_id: 0, + complete: false, + chosen_security_type: None, + + tc_server_protocol_version: None, + ts_client_protocol_version: None, + tc_supported_security_types: None, + ts_security_type_selection: None, + tc_server_security_type: None, + tc_vnc_challenge: None, + ts_vnc_response: None, + ts_client_init: None, + tc_security_result: None, + tc_failure_reason: None, + tc_server_init: None, + + tx_data: applayer::AppLayerTxData::new(), + } + } + + fn set_event(&mut self, event: RFBEvent) { + self.tx_data.set_event(event as u8); + } +} + +pub struct RFBState { + state_data: AppLayerStateData, + tx_id: u64, + transactions: Vec<RFBTransaction>, + state: parser::RFBGlobalState, +} + +impl State<RFBTransaction> for RFBState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&RFBTransaction> { + self.transactions.get(index) + } +} + +impl Default for RFBState { + fn default() -> Self { + Self::new() + } +} + +impl RFBState { + pub fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + tx_id: 0, + transactions: Vec::new(), + state: parser::RFBGlobalState::TCServerProtocolVersion, + } + } + + // Free a transaction by ID. + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&RFBTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + fn new_tx(&mut self) -> RFBTransaction { + let mut tx = RFBTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn get_current_tx(&mut self) -> Option<&mut RFBTransaction> { + let tx_id = self.tx_id; + self.transactions.iter_mut().find(|tx| tx.tx_id == tx_id) + } + + fn parse_request(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + let mut current = input; + let mut consumed = 0; + SCLogDebug!("request_state {}, input_len {}", self.state, input.len()); + loop { + if current.is_empty() { + return AppLayerResult::ok(); + } + match self.state { + parser::RFBGlobalState::TSClientProtocolVersion => { + match parser::parse_protocol_version(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + if request.major == "003" && request.minor == "003" { + // in version 3.3 the server decided security type + self.state = parser::RFBGlobalState::TCServerSecurityType; + } else { + self.state = parser::RFBGlobalState::TCSupportedSecurityTypes; + } + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.ts_client_protocol_version = Some(request); + } else { + debug_validate_fail!( + "no transaction set at protocol selection stage" + ); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + // We even failed to parse the protocol version. + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TSSecurityTypeSelection => { + match parser::parse_security_type_selection(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + let chosen_security_type = request.security_type; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.ts_security_type_selection = Some(request); + current_transaction.chosen_security_type = + Some(chosen_security_type as u32); + } else { + debug_validate_fail!("no transaction set at security type stage"); + } + + match chosen_security_type { + 2 => self.state = parser::RFBGlobalState::TCVncChallenge, + 1 => self.state = parser::RFBGlobalState::TSClientInit, + _ => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction + .set_event(RFBEvent::UnimplementedSecurityType); + } + // We have just have seen a security type we don't know about. + // This is not bad per se, it might just mean this is a + // proprietary one not in the spec. + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // We failed to parse the security type. + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::TSVncResponse => match parser::parse_vnc_auth(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + self.state = parser::RFBGlobalState::TCSecurityResult; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.ts_vnc_response = Some(request); + } else { + debug_validate_fail!("no transaction set at security result stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + }, + parser::RFBGlobalState::TSClientInit => match parser::parse_client_init(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + self.state = parser::RFBGlobalState::TCServerInit; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.ts_client_init = Some(request); + } else { + debug_validate_fail!("no transaction set at client init stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // We failed to parse the client init. + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + }, + parser::RFBGlobalState::Skip => { + // End of parseable handshake reached, skip rest of traffic + return AppLayerResult::ok(); + } + _ => { + // We have gotten out of sync with the expected state flow. + // This could happen since we use a global state (i.e. that + // is used for both directions), but if traffic can not be + // parsed as expected elsewhere, we might not have advanced + // a state for one direction but received data in the + // "unexpected" direction, causing the parser to end up + // here. Let's stop trying to parse the traffic but still + // accept it. + SCLogDebug!("Invalid state for request: {}", self.state); + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::ConfusedState); + current_transaction.complete = true; + } + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + } + + fn parse_response(&mut self, flow: *const Flow, stream_slice: StreamSlice) -> AppLayerResult { + let input = stream_slice.as_slice(); + // We're not interested in empty responses. + if input.is_empty() { + return AppLayerResult::ok(); + } + + let mut current = input; + let mut consumed = 0; + SCLogDebug!( + "response_state {}, response_len {}", + self.state, + input.len() + ); + loop { + if current.is_empty() { + return AppLayerResult::ok(); + } + match self.state { + parser::RFBGlobalState::TCServerProtocolVersion => { + match parser::parse_protocol_version(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + self.state = parser::RFBGlobalState::TSClientProtocolVersion; + let tx = self.new_tx(); + self.transactions.push(tx); + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_server_protocol_version = Some(request); + } else { + debug_validate_fail!("no transaction set but we just set one"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + // We even failed to parse the protocol version. + return AppLayerResult::err(); + } + } + } + parser::RFBGlobalState::TCSupportedSecurityTypes => { + match parser::parse_supported_security_types(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + SCLogDebug!( + "supported_security_types: {}, types: {}", + request.number_of_types, + request + .types + .iter() + .map(ToString::to_string) + .map(|v| v + " ") + .collect::<String>() + ); + + self.state = parser::RFBGlobalState::TSSecurityTypeSelection; + if request.number_of_types == 0 { + self.state = parser::RFBGlobalState::TCFailureReason; + } + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_supported_security_types = Some(request); + } else { + debug_validate_fail!("no transaction set at security type stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::TCServerSecurityType => { + // In RFB 3.3, the server decides the authentication type + match parser::parse_server_security_type(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + let chosen_security_type = request.security_type; + SCLogDebug!("chosen_security_type: {}", chosen_security_type); + match chosen_security_type { + 0 => self.state = parser::RFBGlobalState::TCFailureReason, + 1 => self.state = parser::RFBGlobalState::TSClientInit, + 2 => self.state = parser::RFBGlobalState::TCVncChallenge, + _ => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction + .set_event(RFBEvent::UnimplementedSecurityType); + current_transaction.complete = true; + } else { + debug_validate_fail!( + "no transaction set at security type stage" + ); + } + // We have just have seen a security type we don't know about. + // This is not bad per se, it might just mean this is a + // proprietary one not in the spec. + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_server_security_type = Some(request); + current_transaction.chosen_security_type = + Some(chosen_security_type); + } else { + debug_validate_fail!("no transaction set at security type stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::TCVncChallenge => match parser::parse_vnc_auth(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + self.state = parser::RFBGlobalState::TSVncResponse; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_vnc_challenge = Some(request); + } else { + debug_validate_fail!("no transaction set at auth stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + }, + parser::RFBGlobalState::TCSecurityResult => { + match parser::parse_security_result(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + if request.status == 0 { + self.state = parser::RFBGlobalState::TSClientInit; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_security_result = Some(request); + } else { + debug_validate_fail!( + "no transaction set at security result stage" + ); + } + } else if request.status == 1 { + self.state = parser::RFBGlobalState::TCFailureReason; + } else { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::UnknownSecurityResult); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::TCFailureReason => { + match parser::parse_failure_reason(current) { + Ok((_rem, request)) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_failure_reason = Some(request); + } else { + debug_validate_fail!("no transaction set at failure reason stage"); + } + return AppLayerResult::ok(); + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::TCServerInit => { + match parser::parse_server_init(current) { + Ok((rem, request)) => { + consumed += current.len() - rem.len(); + let _pdu = Frame::new( + flow, + &stream_slice, + current, + consumed as i64, + RFBFrameType::Pdu as u8, + ); + + current = rem; + + self.state = parser::RFBGlobalState::Skip; + + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.tc_server_init = Some(request); + // connection initialization is complete and parsed + current_transaction.complete = true; + } else { + debug_validate_fail!("no transaction set at server init stage"); + } + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete( + consumed as u32, + (current.len() + 1) as u32, + ); + } + Err(_) => { + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::MalformedMessage); + current_transaction.complete = true; + } + // Continue the flow but stop trying to map the protocol. + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + parser::RFBGlobalState::Skip => { + //todo implement RFB messages, for now we stop here + return AppLayerResult::ok(); + } + _ => { + // We have gotten out of sync with the expected state flow. + // This could happen since we use a global state (i.e. that + // is used for both directions), but if traffic can not be + // parsed as expected elsewhere, we might not have advanced + // a state for one direction but received data in the + // "unexpected" direction, causing the parser to end up + // here. Let's stop trying to parse the traffic but still + // accept it. + SCLogDebug!("Invalid state for response: {}", self.state); + if let Some(current_transaction) = self.get_current_tx() { + current_transaction.set_event(RFBEvent::ConfusedState); + current_transaction.complete = true; + } + self.state = parser::RFBGlobalState::Skip; + return AppLayerResult::ok(); + } + } + } + } +} + +// C exports. + +#[no_mangle] +pub extern "C" fn rs_rfb_state_new( + _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, +) -> *mut std::os::raw::c_void { + let state = RFBState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_rfb_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + std::mem::drop(unsafe { Box::from_raw(state as *mut RFBState) }); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, RFBState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_parse_request( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, RFBState); + return state.parse_request(flow, stream_slice); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_parse_response( + flow: *const Flow, state: *mut std::os::raw::c_void, _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, RFBState); + return state.parse_response(flow, stream_slice); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_state_get_tx( + state: *mut std::os::raw::c_void, tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, RFBState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, RFBState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, _direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, RFBTransaction); + if tx.complete { + return 1; + } + return 0; +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"rfb\0"; + +export_tx_data_get!(rs_rfb_get_tx_data, RFBTransaction); +export_state_data_get!(rs_rfb_get_state_data, RFBState); + +#[no_mangle] +pub unsafe extern "C" fn rs_rfb_register_parser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_rfb_state_new, + state_free: rs_rfb_state_free, + tx_free: rs_rfb_state_tx_free, + parse_ts: rs_rfb_parse_request, + parse_tc: rs_rfb_parse_response, + get_tx_count: rs_rfb_state_get_tx_count, + get_tx: rs_rfb_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_rfb_tx_get_alstate_progress, + get_eventinfo: Some(RFBEvent::get_event_info), + get_eventinfo_byid: Some(RFBEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<RFBState, RFBTransaction>), + get_tx_data: rs_rfb_get_tx_data, + get_state_data: rs_rfb_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: Some(RFBFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(RFBFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_RFB = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust rfb parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for RFB."); + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::core::STREAM_START; + + #[test] + fn test_error_state() { + let mut state = RFBState::new(); + + let buf: &[u8] = &[ + 0x05, 0x00, 0x03, 0x20, 0x20, 0x18, 0x00, 0x01, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, + 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x61, 0x6e, 0x65, 0x61, + 0x67, 0x6c, 0x65, 0x73, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, + ]; + let r = state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(buf, STREAM_START, 0), + ); + + assert_eq!( + r, + AppLayerResult { + status: -1, + consumed: 0, + needed: 0 + } + ); + } + + // Test the state machine for RFB protocol + // Passes an initial buffer with initial RFBState = TCServerProtocolVersion + // Tests various client and server RFBStates as the buffer is parsed using parse_request and parse_response functions + #[test] + fn test_rfb_state_machine() { + let mut init_state = RFBState::new(); + + let buf: &[u8] = &[ + 0x52, 0x46, 0x42, 0x20, 0x30, 0x30, 0x33, 0x2e, 0x30, 0x30, 0x38, 0x0a, + 0x01, /* Number of security types: 1 */ + 0x02, /* Security type: VNC (2) */ + 0x02, /* Security type selected: VNC (2) */ + 0x54, 0x7b, 0x7a, 0x6f, 0x36, 0xa1, 0x54, 0xdb, 0x03, 0xa2, 0x57, 0x5c, 0x6f, 0x2a, + 0x4e, + 0xc5, /* 16 byte Authentication challenge: 547b7a6f36a154db03a2575c6f2a4ec5 */ + 0x00, 0x00, 0x00, 0x00, /* Authentication result: OK */ + 0x00, /* Share desktop flag: False */ + 0x05, 0x00, 0x03, 0x20, 0x20, 0x18, 0x00, 0x01, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, + 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x61, 0x6e, 0x65, 0x61, + 0x67, 0x6c, 0x65, 0x73, 0x40, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, + 0x2e, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, /* Server framebuffer parameters */ + ]; + + //The buffer values correspond to Server Protocol version: 003.008 + // Same buffer is used for both functions due to similar values in request and response + init_state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(&buf[0..12], STREAM_START, 0), + ); + let mut ok_state = parser::RFBGlobalState::TSClientProtocolVersion; + assert_eq!(init_state.state, ok_state); + + //The buffer values correspond to Client Protocol version: 003.008 + init_state.parse_request( + std::ptr::null(), + StreamSlice::from_slice(&buf[0..12], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TCSupportedSecurityTypes; + assert_eq!(init_state.state, ok_state); + + init_state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(&buf[12..14], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TSSecurityTypeSelection; + assert_eq!(init_state.state, ok_state); + + init_state.parse_request( + std::ptr::null(), + StreamSlice::from_slice(&buf[14..15], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TCVncChallenge; + assert_eq!(init_state.state, ok_state); + + //The buffer values correspond to Server Authentication challenge: 547b7a6f36a154db03a2575c6f2a4ec5 + // Same buffer is used for both functions due to similar values in request and response + init_state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(&buf[15..31], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TSVncResponse; + assert_eq!(init_state.state, ok_state); + + //The buffer values correspond to Client Authentication response: 547b7a6f36a154db03a2575c6f2a4ec5 + init_state.parse_request( + std::ptr::null(), + StreamSlice::from_slice(&buf[15..31], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TCSecurityResult; + assert_eq!(init_state.state, ok_state); + + init_state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(&buf[31..35], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TSClientInit; + assert_eq!(init_state.state, ok_state); + + init_state.parse_request( + std::ptr::null(), + StreamSlice::from_slice(&buf[35..36], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::TCServerInit; + assert_eq!(init_state.state, ok_state); + + init_state.parse_response( + std::ptr::null(), + StreamSlice::from_slice(&buf[36..90], STREAM_START, 0), + ); + ok_state = parser::RFBGlobalState::Skip; + assert_eq!(init_state.state, ok_state); + } +} diff --git a/rust/src/sip/detect.rs b/rust/src/sip/detect.rs new file mode 100644 index 0000000..63f6365 --- /dev/null +++ b/rust/src/sip/detect.rs @@ -0,0 +1,182 @@ +/* Copyright (C) 2019 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 Giuseppe Longo <giuseppe@glongo.it> + +use crate::core::Direction; +use crate::sip::sip::SIPTransaction; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_method( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.request { + let m = &r.method; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_uri( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.request { + let p = &r.path; + if !p.is_empty() { + *buffer = p.as_ptr(); + *buffer_len = p.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_protocol( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, + direction: u8, +) -> u8 { + match direction.into() { + Direction::ToServer => { + if let Some(ref r) = tx.request { + let v = &r.version; + if !v.is_empty() { + *buffer = v.as_ptr(); + *buffer_len = v.len() as u32; + return 1; + } + } + } + Direction::ToClient => { + if let Some(ref r) = tx.response { + let v = &r.version; + if !v.is_empty() { + *buffer = v.as_ptr(); + *buffer_len = v.len() as u32; + return 1; + } + } + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_stat_code( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.response { + let c = &r.code; + if !c.is_empty() { + *buffer = c.as_ptr(); + *buffer_len = c.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_stat_msg( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.response { + let re = &r.reason; + if !re.is_empty() { + *buffer = re.as_ptr(); + *buffer_len = re.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_request_line( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.request_line { + if !r.is_empty() { + *buffer = r.as_ptr(); + *buffer_len = r.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_tx_get_response_line( + tx: &mut SIPTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32, +) -> u8 { + if let Some(ref r) = tx.response_line { + if !r.is_empty() { + *buffer = r.as_ptr(); + *buffer_len = r.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} diff --git a/rust/src/sip/log.rs b/rust/src/sip/log.rs new file mode 100644 index 0000000..792acfa --- /dev/null +++ b/rust/src/sip/log.rs @@ -0,0 +1,54 @@ +/* Copyright (C) 2019 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 Giuseppe Longo <giuseppe@glongo.it> + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::sip::sip::SIPTransaction; + +fn log(tx: &SIPTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("sip")?; + + if let Some(req) = &tx.request { + js.set_string("method", &req.method)? + .set_string("uri", &req.path)? + .set_string("version", &req.version)?; + } + + if let Some(req_line) = &tx.request_line { + js.set_string("request_line", req_line)?; + } + + if let Some(resp) = &tx.response { + js.set_string("version", &resp.version)? + .set_string("code", &resp.code)? + .set_string("reason", &resp.reason)?; + } + + if let Some(resp_line) = &tx.response_line { + js.set_string("response_line", resp_line)?; + } + + js.close()?; + + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_sip_log_json(tx: &mut SIPTransaction, js: &mut JsonBuilder) -> bool { + log(tx, js).is_ok() +}
\ No newline at end of file diff --git a/rust/src/sip/mod.rs b/rust/src/sip/mod.rs new file mode 100755 index 0000000..de63aaa --- /dev/null +++ b/rust/src/sip/mod.rs @@ -0,0 +1,25 @@ +/* Copyright (C) 2019 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. + */ + +//! SIP protocol parser, detection and logger module. + +// written by Giuseppe Longo <giuseppe@glongo.it> + +pub mod detect; +pub mod log; +pub mod parser; +pub mod sip; diff --git a/rust/src/sip/parser.rs b/rust/src/sip/parser.rs new file mode 100644 index 0000000..a34bc26 --- /dev/null +++ b/rust/src/sip/parser.rs @@ -0,0 +1,325 @@ +/* Copyright (C) 2019-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Giuseppe Longo <giuseppe@glono.it> + +use nom7::bytes::streaming::{take, take_while, take_while1}; +use nom7::character::streaming::{char, crlf}; +use nom7::character::{is_alphabetic, is_alphanumeric, is_space}; +use nom7::combinator::map_res; +use nom7::sequence::delimited; +use nom7::{Err, IResult, Needed}; +use std; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct Header { + pub name: String, + pub value: String, +} + +#[derive(Debug)] +pub struct Request { + pub method: String, + pub path: String, + pub version: String, + pub headers: HashMap<String, String>, + + pub request_line_len: u16, + pub headers_len: u16, + pub body_offset: u16, + pub body_len: u16, +} + +#[derive(Debug)] +pub struct Response { + pub version: String, + pub code: String, + pub reason: String, + + pub response_line_len: u16, + pub headers_len: u16, + pub body_offset: u16, + pub body_len: u16, +} + +#[inline] +fn is_token_char(b: u8) -> bool { + is_alphanumeric(b) || b"!%'*+-._`".contains(&b) +} + +#[inline] +fn is_method_char(b: u8) -> bool { + is_alphabetic(b) +} + +#[inline] +fn is_request_uri_char(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"~#@:".contains(&b) +} + +#[inline] +fn is_version_char(b: u8) -> bool { + is_alphanumeric(b) || b"./".contains(&b) +} + +#[inline] +fn is_reason_phrase(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"$&(),/:;=?@[\\]^ ".contains(&b) +} + +fn is_header_name(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) +} + +fn is_header_value(b: u8) -> bool { + is_alphanumeric(b) || is_token_char(b) || b"\"#$&(),/;:<=>?@[]{}()^|~\\\t\n\r ".contains(&b) +} + +pub fn sip_parse_request(oi: &[u8]) -> IResult<&[u8], Request> { + let (i, method) = parse_method(oi)?; + let (i, _) = char(' ')(i)?; + let (i, path) = parse_request_uri(i)?; + let (i, _) = char(' ')(i)?; + let (i, version) = parse_version(i)?; + let (hi, _) = crlf(i)?; + let request_line_len = oi.len() - hi.len(); + let (phi, headers) = parse_headers(hi)?; + let headers_len = hi.len() - phi.len(); + let (bi, _) = crlf(phi)?; + let body_offset = oi.len() - bi.len(); + Ok(( + bi, + Request { + method: method.into(), + path: path.into(), + version: version.into(), + headers, + + request_line_len: request_line_len as u16, + headers_len: headers_len as u16, + body_offset: body_offset as u16, + body_len: bi.len() as u16, + }, + )) +} + +pub fn sip_parse_response(oi: &[u8]) -> IResult<&[u8], Response> { + let (i, version) = parse_version(oi)?; + let (i, _) = char(' ')(i)?; + let (i, code) = parse_code(i)?; + let (i, _) = char(' ')(i)?; + let (i, reason) = parse_reason(i)?; + let (hi, _) = crlf(i)?; + let response_line_len = oi.len() - hi.len(); + let (phi, _headers) = parse_headers(hi)?; + let headers_len = hi.len() - phi.len(); + let (bi, _) = crlf(phi)?; + let body_offset = oi.len() - bi.len(); + Ok(( + bi, + Response { + version: version.into(), + code: code.into(), + reason: reason.into(), + + response_line_len: response_line_len as u16, + headers_len: headers_len as u16, + body_offset: body_offset as u16, + body_len: bi.len() as u16, + }, + )) +} + +#[inline] +fn parse_method(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_method_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_request_uri(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while1(is_request_uri_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_version(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while1(is_version_char), std::str::from_utf8)(i) +} + +#[inline] +fn parse_code(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take(3_usize), std::str::from_utf8)(i) +} + +#[inline] +fn parse_reason(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_reason_phrase), std::str::from_utf8)(i) +} + +#[inline] +fn header_name(i: &[u8]) -> IResult<&[u8], &str> { + map_res(take_while(is_header_name), std::str::from_utf8)(i) +} + +#[inline] +fn header_value(i: &[u8]) -> IResult<&[u8], &str> { + map_res(parse_header_value, std::str::from_utf8)(i) +} + +#[inline] +fn hcolon(i: &[u8]) -> IResult<&[u8], char> { + delimited(take_while(is_space), char(':'), take_while(is_space))(i) +} + +fn message_header(i: &[u8]) -> IResult<&[u8], Header> { + let (i, n) = header_name(i)?; + let (i, _) = hcolon(i)?; + let (i, v) = header_value(i)?; + let (i, _) = crlf(i)?; + Ok(( + i, + Header { + name: String::from(n), + value: String::from(v), + }, + )) +} + +pub fn sip_take_line(i: &[u8]) -> IResult<&[u8], Option<String>> { + let (i, line) = map_res(take_while1(is_reason_phrase), std::str::from_utf8)(i)?; + Ok((i, Some(line.into()))) +} + +pub fn parse_headers(mut input: &[u8]) -> IResult<&[u8], HashMap<String, String>> { + let mut headers_map: HashMap<String, String> = HashMap::new(); + loop { + match crlf(input) as IResult<&[u8], _> { + Ok((_, _)) => { + break; + } + Err(Err::Error(_)) => {} + Err(Err::Failure(_)) => {} + Err(Err::Incomplete(e)) => return Err(Err::Incomplete(e)), + }; + let (rest, header) = message_header(input)?; + headers_map.insert(header.name, header.value); + input = rest; + } + + Ok((input, headers_map)) +} + +fn parse_header_value(buf: &[u8]) -> IResult<&[u8], &[u8]> { + let mut end_pos = 0; + let mut trail_spaces = 0; + let mut idx = 0; + while idx < buf.len() { + match buf[idx] { + b'\n' => { + idx += 1; + if idx >= buf.len() { + return Err(Err::Incomplete(Needed::new(1))); + } + match buf[idx] { + b' ' | b'\t' => { + idx += 1; + continue; + } + _ => { + return Ok((&buf[(end_pos + trail_spaces)..], &buf[..end_pos])); + } + } + } + b' ' | b'\t' => { + trail_spaces += 1; + } + b'\r' => {} + b => { + trail_spaces = 0; + if !is_header_value(b) { + return Err(Err::Incomplete(Needed::new(1))); + } + end_pos = idx + 1; + } + } + idx += 1; + } + Ok((&b""[..], buf)) +} + +#[cfg(test)] +mod tests { + + use crate::sip::parser::*; + + #[test] + fn test_parse_request() { + let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ + From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\ + To: <sip:voi18063@sip.cybercity.dk>\r\n\ + Content-Length: 0\r\n\ + \r\n" + .as_bytes(); + + match sip_parse_request(buf) { + Ok((_, req)) => { + assert_eq!(req.method, "REGISTER"); + assert_eq!(req.path, "sip:sip.cybercity.dk"); + assert_eq!(req.version, "SIP/2.0"); + assert_eq!(req.headers["Content-Length"], "0"); + } + _ => { + assert!(false); + } + } + } + + #[test] + fn test_parse_request_trail_space_header() { + let buf: &[u8] = "REGISTER sip:sip.cybercity.dk SIP/2.0\r\n\ + From: <sip:voi18063@sip.cybercity.dk>;tag=903df0a\r\n\ + To: <sip:voi18063@sip.cybercity.dk>\r\n\ + Content-Length: 4 \r\n\ + \r\nABCD" + .as_bytes(); + + let (body, req) = sip_parse_request(buf).expect("parsing failed"); + assert_eq!(req.method, "REGISTER"); + assert_eq!(req.path, "sip:sip.cybercity.dk"); + assert_eq!(req.version, "SIP/2.0"); + assert_eq!(req.headers["Content-Length"], "4"); + assert_eq!(body, "ABCD".as_bytes()); + } + + #[test] + fn test_parse_response() { + let buf: &[u8] = "SIP/2.0 401 Unauthorized\r\n\ + \r\n" + .as_bytes(); + + match sip_parse_response(buf) { + Ok((_, resp)) => { + assert_eq!(resp.version, "SIP/2.0"); + assert_eq!(resp.code, "401"); + assert_eq!(resp.reason, "Unauthorized"); + } + _ => { + assert!(false); + } + } + } +} diff --git a/rust/src/sip/sip.rs b/rust/src/sip/sip.rs new file mode 100755 index 0000000..4e86f5e --- /dev/null +++ b/rust/src/sip/sip.rs @@ -0,0 +1,391 @@ +/* Copyright (C) 2019-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Giuseppe Longo <giuseppe@glongo.it> + +use crate::frames::*; +use crate::applayer::{self, *}; +use crate::core; +use crate::core::{AppProto, Flow, ALPROTO_UNKNOWN}; +use crate::sip::parser::*; +use nom7::Err; +use std; +use std::ffi::CString; + +// app-layer-frame-documentation tag start: FrameType enum +#[derive(AppLayerFrameType)] +pub enum SIPFrameType { + Pdu, + RequestLine, + ResponseLine, + RequestHeaders, + ResponseHeaders, + RequestBody, + ResponseBody, +} +// app-layer-frame-documentation tag end: FrameType enum + +#[derive(AppLayerEvent)] +pub enum SIPEvent { + IncompleteData, + InvalidData, +} + +#[derive(Default)] +pub struct SIPState { + state_data: AppLayerStateData, + transactions: Vec<SIPTransaction>, + tx_id: u64, +} + +impl State<SIPTransaction> for SIPState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&SIPTransaction> { + self.transactions.get(index) + } +} + +pub struct SIPTransaction { + id: u64, + pub request: Option<Request>, + pub response: Option<Response>, + pub request_line: Option<String>, + pub response_line: Option<String>, + tx_data: applayer::AppLayerTxData, +} + +impl Transaction for SIPTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl SIPState { + pub fn new() -> SIPState { + Default::default() + } + + pub fn free(&mut self) { + self.transactions.clear(); + } + + fn new_tx(&mut self, _direction: crate::core::Direction) -> SIPTransaction { + self.tx_id += 1; + SIPTransaction::new(self.tx_id) + } + + fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SIPTransaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self + .transactions + .iter() + .position(|tx| tx.id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + fn set_event(&mut self, event: SIPEvent) { + if let Some(tx) = self.transactions.last_mut() { + tx.tx_data.set_event(event as u8); + } + } + + // app-layer-frame-documentation tag start: parse_request + fn parse_request(&mut self, flow: *const core::Flow, stream_slice: StreamSlice) -> bool { + let input = stream_slice.as_slice(); + let _pdu = Frame::new( + flow, + &stream_slice, + input, + input.len() as i64, + SIPFrameType::Pdu as u8, + ); + SCLogDebug!("ts: pdu {:?}", _pdu); + + match sip_parse_request(input) { + Ok((_, request)) => { + sip_frames_ts(flow, &stream_slice, &request); + let mut tx = self.new_tx(crate::core::Direction::ToServer); + tx.request = Some(request); + if let Ok((_, req_line)) = sip_take_line(input) { + tx.request_line = req_line; + } + self.transactions.push(tx); + return true; + } + // app-layer-frame-documentation tag end: parse_request + Err(Err::Incomplete(_)) => { + self.set_event(SIPEvent::IncompleteData); + return false; + } + Err(_) => { + self.set_event(SIPEvent::InvalidData); + return false; + } + } + } + + fn parse_response(&mut self, flow: *const core::Flow, stream_slice: StreamSlice) -> bool { + let input = stream_slice.as_slice(); + let _pdu = Frame::new(flow, &stream_slice, input, input.len() as i64, SIPFrameType::Pdu as u8); + SCLogDebug!("tc: pdu {:?}", _pdu); + + match sip_parse_response(input) { + Ok((_, response)) => { + sip_frames_tc(flow, &stream_slice, &response); + let mut tx = self.new_tx(crate::core::Direction::ToClient); + tx.response = Some(response); + if let Ok((_, resp_line)) = sip_take_line(input) { + tx.response_line = resp_line; + } + self.transactions.push(tx); + return true; + } + Err(Err::Incomplete(_)) => { + self.set_event(SIPEvent::IncompleteData); + return false; + } + Err(_) => { + self.set_event(SIPEvent::InvalidData); + return false; + } + } + } +} + +impl SIPTransaction { + pub fn new(id: u64) -> SIPTransaction { + SIPTransaction { + id, + request: None, + response: None, + request_line: None, + response_line: None, + tx_data: applayer::AppLayerTxData::new(), + } + } +} + +// app-layer-frame-documentation tag start: function to add frames +fn sip_frames_ts(flow: *const core::Flow, stream_slice: &StreamSlice, r: &Request) { + let oi = stream_slice.as_slice(); + let _f = Frame::new( + flow, + stream_slice, + oi, + r.request_line_len as i64, + SIPFrameType::RequestLine as u8, + ); + SCLogDebug!("ts: request_line {:?}", _f); + let hi = &oi[r.request_line_len as usize..]; + let _f = Frame::new( + flow, + stream_slice, + hi, + r.headers_len as i64, + SIPFrameType::RequestHeaders as u8, + ); + SCLogDebug!("ts: request_headers {:?}", _f); + if r.body_len > 0 { + let bi = &oi[r.body_offset as usize..]; + let _f = Frame::new( + flow, + stream_slice, + bi, + r.body_len as i64, + SIPFrameType::RequestBody as u8, + ); + SCLogDebug!("ts: request_body {:?}", _f); + } +} +// app-layer-frame-documentation tag end: function to add frames + +fn sip_frames_tc(flow: *const core::Flow, stream_slice: &StreamSlice, r: &Response) { + let oi = stream_slice.as_slice(); + let _f = Frame::new(flow, stream_slice, oi, r.response_line_len as i64, SIPFrameType::ResponseLine as u8); + let hi = &oi[r.response_line_len as usize ..]; + SCLogDebug!("tc: response_line {:?}", _f); + let _f = Frame::new(flow, stream_slice, hi, r.headers_len as i64, SIPFrameType::ResponseHeaders as u8); + SCLogDebug!("tc: response_headers {:?}", _f); + if r.body_len > 0 { + let bi = &oi[r.body_offset as usize ..]; + let _f = Frame::new(flow, stream_slice, bi, r.body_len as i64, SIPFrameType::ResponseBody as u8); + SCLogDebug!("tc: response_body {:?}", _f); + } +} + +#[no_mangle] +pub extern "C" fn rs_sip_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = SIPState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_sip_state_free(state: *mut std::os::raw::c_void) { + let mut state = unsafe { Box::from_raw(state as *mut SIPState) }; + state.free(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_state_get_tx( + state: *mut std::os::raw::c_void, + tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, SIPState); + match state.get_tx_by_id(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { + let state = cast_pointer!(state, SIPState); + state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { + let state = cast_pointer!(state, SIPState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_sip_tx_get_alstate_progress( + _tx: *mut std::os::raw::c_void, + _direction: u8, +) -> std::os::raw::c_int { + 1 +} + +static mut ALPROTO_SIP: AppProto = ALPROTO_UNKNOWN; + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_probing_parser_ts( + _flow: *const Flow, + _direction: u8, + input: *const u8, + input_len: u32, + _rdir: *mut u8, +) -> AppProto { + let buf = build_slice!(input, input_len as usize); + if sip_parse_request(buf).is_ok() { + return ALPROTO_SIP; + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_probing_parser_tc( + _flow: *const Flow, + _direction: u8, + input: *const u8, + input_len: u32, + _rdir: *mut u8, +) -> AppProto { + let buf = build_slice!(input, input_len as usize); + if sip_parse_response(buf).is_ok() { + return ALPROTO_SIP; + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_parse_request( + flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, SIPState); + state.parse_request(flow, stream_slice).into() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_parse_response( + flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, +) -> AppLayerResult { + let state = cast_pointer!(state, SIPState); + state.parse_response(flow, stream_slice).into() +} + +export_tx_data_get!(rs_sip_get_tx_data, SIPTransaction); +export_state_data_get!(rs_sip_get_state_data, SIPState); + +const PARSER_NAME: &[u8] = b"sip\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_sip_register_parser() { + let default_port = CString::new("[5060,5061]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: core::IPPROTO_UDP, + probe_ts: Some(rs_sip_probing_parser_ts), + probe_tc: Some(rs_sip_probing_parser_tc), + min_depth: 0, + max_depth: 16, + state_new: rs_sip_state_new, + state_free: rs_sip_state_free, + tx_free: rs_sip_state_tx_free, + parse_ts: rs_sip_parse_request, + parse_tc: rs_sip_parse_response, + get_tx_count: rs_sip_state_get_tx_count, + get_tx: rs_sip_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_sip_tx_get_alstate_progress, + get_eventinfo: Some(SIPEvent::get_event_info), + get_eventinfo_byid: Some(SIPEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<SIPState, SIPTransaction>), + get_tx_data: rs_sip_get_tx_data, + get_state_data: rs_sip_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: Some(SIPFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(SIPFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_SIP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detecter and parser disabled for SIP/UDP."); + } +} diff --git a/rust/src/smb/auth.rs b/rust/src/smb/auth.rs new file mode 100644 index 0000000..c5d20bb --- /dev/null +++ b/rust/src/smb/auth.rs @@ -0,0 +1,260 @@ +/* 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. + */ + +use crate::kerberos::*; + +use crate::smb::ntlmssp_records::*; +use crate::smb::smb::*; + +use nom7::{Err, IResult}; +use der_parser6::ber::BerObjectContent; +use der_parser6::der::{parse_der_oid, parse_der_sequence}; + +fn parse_secblob_get_spnego(blob: &[u8]) -> IResult<&[u8], &[u8], SecBlobError> +{ + let (rem, base_o) = der_parser6::parse_der(blob).map_err(Err::convert)?; + SCLogDebug!("parse_secblob_get_spnego: base_o {:?}", base_o); + let d = match base_o.content.as_slice() { + Err(_) => { return Err(Err::Error(SecBlobError::NotSpNego)); }, + Ok(d) => d, + }; + let (next, o) = parse_der_oid(d).map_err(Err::convert)?; + SCLogDebug!("parse_secblob_get_spnego: sub_o {:?}", o); + + let oid = match o.content.as_oid() { + Ok(oid) => oid, + Err(_) => { + return Err(Err::Error(SecBlobError::NotSpNego)); + }, + }; + SCLogDebug!("oid {}", oid.to_string()); + + match oid.to_string().as_str() { + "1.3.6.1.5.5.2" => { + SCLogDebug!("SPNEGO {}", oid); + }, + _ => { + return Err(Err::Error(SecBlobError::NotSpNego)); + }, + } + + SCLogDebug!("parse_secblob_get_spnego: next {:?}", next); + SCLogDebug!("parse_secblob_get_spnego: DONE"); + Ok((rem, next)) +} + +fn parse_secblob_spnego_start(blob: &[u8]) -> IResult<&[u8], &[u8], SecBlobError> +{ + let (rem, o) = der_parser6::parse_der(blob).map_err(Err::convert)?; + let d = match o.content.as_slice() { + Ok(d) => { + SCLogDebug!("d: next data len {}",d.len()); + d + }, + _ => { + return Err(Err::Error(SecBlobError::NotSpNego)); + }, + }; + Ok((rem, d)) +} + +#[derive(Debug, PartialEq)] +pub struct SpnegoRequest { + pub krb: Option<Kerberos5Ticket>, + pub ntlmssp: Option<NtlmsspData>, +} + +fn parse_secblob_spnego(blob: &[u8]) -> Option<SpnegoRequest> +{ + let mut have_ntlmssp = false; + let mut have_kerberos = false; + let mut kticket : Option<Kerberos5Ticket> = None; + let mut ntlmssp : Option<NtlmsspData> = None; + + let o = match parse_der_sequence(blob) { + Ok((_, o)) => o, + _ => { return None; }, + }; + for s in o { + SCLogDebug!("s {:?}", s); + + let n = match s.content.as_slice() { + Ok(s) => s, + _ => { continue; }, + }; + let o = match der_parser6::parse_der(n) { + Ok((_,x)) => x, + _ => { continue; }, + }; + SCLogDebug!("o {:?}", o); + match o.content { + BerObjectContent::Sequence(ref seq) => { + for se in seq { + SCLogDebug!("SEQ {:?}", se); + match se.content { + BerObjectContent::OID(ref oid) => { + SCLogDebug!("OID {:?}", oid); + match oid.to_string().as_str() { + "1.2.840.48018.1.2.2" => { SCLogDebug!("Microsoft Kerberos 5"); }, + "1.2.840.113554.1.2.2" => { SCLogDebug!("Kerberos 5"); have_kerberos = true; }, + "1.2.840.113554.1.2.2.1" => { SCLogDebug!("krb5-name"); }, + "1.2.840.113554.1.2.2.2" => { SCLogDebug!("krb5-principal"); }, + "1.2.840.113554.1.2.2.3" => { SCLogDebug!("krb5-user-to-user-mech"); }, + "1.3.6.1.4.1.311.2.2.10" => { SCLogDebug!("NTLMSSP"); have_ntlmssp = true; }, + "1.3.6.1.4.1.311.2.2.30" => { SCLogDebug!("NegoEx"); }, + _ => { SCLogDebug!("unexpected OID {:?}", oid); }, + } + }, + _ => { SCLogDebug!("expected OID, got {:?}", se); }, + } + } + }, + BerObjectContent::OctetString(os) => { + if have_kerberos { + if let Ok((_, t)) = parse_kerberos5_request(os) { + kticket = Some(t) + } + } + + if have_ntlmssp && kticket.is_none() { + SCLogDebug!("parsing expected NTLMSSP"); + ntlmssp = parse_ntlmssp_blob(os); + } + }, + _ => {}, + } + } + + let s = SpnegoRequest { + krb: kticket, + ntlmssp, + }; + Some(s) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct NtlmsspData { + pub host: Vec<u8>, + pub user: Vec<u8>, + pub domain: Vec<u8>, + pub version: Option<NTLMSSPVersion>, + pub warning: bool, +} + +/// take in blob, search for the header and parse it +fn parse_ntlmssp_blob(blob: &[u8]) -> Option<NtlmsspData> +{ + let mut ntlmssp_data : Option<NtlmsspData> = None; + + SCLogDebug!("NTLMSSP {:?}", blob); + if let Ok((_, nd)) = parse_ntlmssp(blob) { + SCLogDebug!("NTLMSSP TYPE {}/{} nd {:?}", + nd.msg_type, &ntlmssp_type_string(nd.msg_type), nd); + match nd.msg_type { + NTLMSSP_NEGOTIATE => { + }, + NTLMSSP_AUTH => { + if let Ok((_, ad)) = parse_ntlm_auth_record(nd.data) { + SCLogDebug!("auth data {:?}", ad); + let mut host = ad.host.to_vec(); + host.retain(|&i|i != 0x00); + let mut user = ad.user.to_vec(); + user.retain(|&i|i != 0x00); + let mut domain = ad.domain.to_vec(); + domain.retain(|&i|i != 0x00); + + let d = NtlmsspData { + host, + user, + domain, + warning: ad.warning, + version: ad.version, + }; + ntlmssp_data = Some(d); + } + }, + _ => {}, + } + } + return ntlmssp_data; +} + +// if spnego parsing fails try to fall back to ntlmssp +pub fn parse_secblob(blob: &[u8]) -> Option<SpnegoRequest> +{ + match parse_secblob_get_spnego(blob) { + Ok((_, spnego)) => { + match parse_secblob_spnego_start(spnego) { + Ok((_, spnego_start)) => { + parse_secblob_spnego(spnego_start) + }, + _ => { + match parse_ntlmssp_blob(blob) { + Some(n) => { + let s = SpnegoRequest { + krb: None, + ntlmssp: Some(n), + }; + Some(s) + }, + None => { None }, + } + }, + } + }, + _ => { + match parse_ntlmssp_blob(blob) { + Some(n) => { + let s = SpnegoRequest { + krb: None, + ntlmssp: Some(n), + }; + Some(s) + }, + None => { None }, + } + }, + } +} +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_secblob() { + // smb2.security_blob + let blob = hex::decode("a18202313082022da0030a0101a28202100482020c4e544c4d5353500003000000180018009c00000048014801b40000001e001e005800000008000800760000001e001e007e00000010001000fc010000158288e20a005a290000000fc6107a73184fb65fe684f6a1641464be4400450053004b0054004f0050002d0032004100450046004d003700470075007300650072004400450053004b0054004f0050002d0032004100450046004d003700470000000000000000000000000000000000000000000000000028a0c9f4e792c408913d2878feaa9a22010100000000000078a7ed218527d2010cf876f08a0b3bfa0000000002001e004400450053004b0054004f0050002d00560031004600410030005500510001001e004400450053004b0054004f0050002d00560031004600410030005500510004001e004400450053004b0054004f0050002d00560031004600410030005500510003001e004400450053004b0054004f0050002d0056003100460041003000550051000700080078a7ed218527d20106000400020000000800300030000000000000000100000000200000ad865b6d08a95d0e76a94e2ca013ab3f69c4fd945cca01b277700fd2b305ca010a001000000000000000000000000000000000000900280063006900660073002f003100390032002e003100360038002e003100390039002e003100330033000000000000000000000000005858824ec4a47b3b42ad3132ab84a5c3a31204100100000092302d756840453f00000000").unwrap(); + let result = parse_secblob(&blob); + assert_eq!( + result, + Some(SpnegoRequest { + krb: None, + ntlmssp: Some(NtlmsspData { + host: b"DESKTOP-2AEFM7G".to_vec(), + user: b"user".to_vec(), + domain: b"DESKTOP-2AEFM7G".to_vec(), + version: Some(NTLMSSPVersion { + ver_major: 10, + ver_minor: 0, + ver_build: 10586, + ver_ntlm_rev: 15, + },), + warning: false, + }), + }) + ); + } +} diff --git a/rust/src/smb/dcerpc.rs b/rust/src/smb/dcerpc.rs new file mode 100644 index 0000000..b4c5749 --- /dev/null +++ b/rust/src/smb/dcerpc.rs @@ -0,0 +1,533 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Victor Julien + +use uuid; +use crate::smb::smb::*; +use crate::smb::smb2::*; +use crate::smb::dcerpc_records::*; +use crate::smb::events::*; +use crate::dcerpc::dcerpc::*; +use crate::smb::smb_status::*; + +impl SMBCommonHdr { + /// helper for DCERPC tx tracking. Check if we need + /// to use the msg_id/multiplex_id in TX tracking. + /// + pub fn to_dcerpc(&self, vercmd: &SMBVerCmdStat) -> SMBCommonHdr { + // only use the msg id for IOCTL, not for READ/WRITE + // as there request/response are different transactions + let mut use_msg_id = self.msg_id; + match vercmd.get_version() { + 2 => { + let (_, cmd2) = vercmd.get_smb2_cmd(); + let x = match cmd2 { + SMB2_COMMAND_READ => { 0 }, + SMB2_COMMAND_WRITE => { 0 }, + SMB2_COMMAND_IOCTL => { self.msg_id }, + _ => { self.msg_id }, + }; + use_msg_id = x; + }, + 1 => { + SCLogDebug!("FIXME TODO"); + //let (_, cmd1) = vercmd.get_smb1_cmd(); + //if cmd1 != SMB1_COMMAND_IOCTL { + use_msg_id = 0; + //} + }, + _ => { }, + } + SMBCommonHdr { + ssn_id: self.ssn_id, + tree_id: self.tree_id, + msg_id: use_msg_id, + rec_type: SMBHDR_TYPE_DCERPCTX, + } + } +} + +#[derive(Default, Debug)] +pub struct DCERPCIface { + pub uuid: Vec<u8>, + pub ver: u16, + pub ver_min: u16, + pub ack_result: u16, + pub ack_reason: u16, + pub acked: bool, + pub context_id: u16, +} + +impl DCERPCIface { + pub fn new(uuid: Vec<u8>, ver: u16, ver_min: u16) -> Self { + Self { + uuid, + ver, + ver_min, + ..Default::default() + } + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionDCERPC { + pub opnum: u16, + pub context_id: u16, + pub req_cmd: u8, + pub req_set: bool, + pub res_cmd: u8, + pub res_set: bool, + pub call_id: u32, + pub frag_cnt_ts: u16, + pub frag_cnt_tc: u16, + pub stub_data_ts: Vec<u8>, + pub stub_data_tc: Vec<u8>, +} + +impl SMBTransactionDCERPC { + fn new_request(req: u8, call_id: u32) -> Self { + return Self { + opnum: 0, + context_id: 0, + req_cmd: req, + req_set: true, + call_id, + ..Default::default() + } + } + fn new_response(call_id: u32) -> Self { + return Self { + call_id, + ..Default::default() + }; + } + pub fn set_result(&mut self, res: u8) { + self.res_set = true; + self.res_cmd = res; + } +} + +impl SMBState { + fn new_dcerpc_tx(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, cmd: u8, call_id: u32) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.vercmd = vercmd; + tx.type_data = Some(SMBTransactionTypeData::DCERPC( + SMBTransactionDCERPC::new_request(cmd, call_id))); + + SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + fn new_dcerpc_tx_for_response(&mut self, hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, call_id: u32) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.vercmd = vercmd; + tx.type_data = Some(SMBTransactionTypeData::DCERPC( + SMBTransactionDCERPC::new_response(call_id))); + + SCLogDebug!("SMB: TX DCERPC created: ID {} hdr {:?}", tx.id, tx.hdr); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + fn get_dcerpc_tx(&mut self, hdr: &SMBCommonHdr, vercmd: &SMBVerCmdStat, call_id: u32) + -> Option<&mut SMBTransaction> + { + let dce_hdr = hdr.to_dcerpc(vercmd); + + SCLogDebug!("looking for {:?}", dce_hdr); + for tx in &mut self.transactions { + let found = dce_hdr.compare(&tx.hdr.to_dcerpc(vercmd)) && + match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + x.call_id == call_id + }, + _ => { false }, + }; + if found { + return Some(tx); + } + } + return None; + } +} + +/// Handle DCERPC request data from a WRITE, IOCTL or TRANS record. +/// return bool indicating whether an tx has been created/updated. +/// +pub fn smb_write_dcerpc_record(state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + data: &[u8]) -> bool +{ + let mut bind_ifaces : Option<Vec<DCERPCIface>> = None; + let mut is_bind = false; + + SCLogDebug!("called for {} bytes of data", data.len()); + match parse_dcerpc_record(data) { + Ok((_, dcer)) => { + SCLogDebug!("DCERPC: version {}.{} write data {} => {:?}", + dcer.version_major, dcer.version_minor, dcer.data.len(), dcer); + + /* if this isn't the first frag, simply update the existing + * tx with the additional stub data */ + if dcer.packet_type == DCERPC_TYPE_REQUEST && !dcer.first_frag { + SCLogDebug!("NOT the first frag. Need to find an existing TX"); + match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) { + Ok((_, recr)) => { + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + SCLogDebug!("previous CMD {} found at tx {} => {:?}", + dcer.packet_type, tx.id, tx); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("additional frag of size {}", recr.data.len()); + tdn.stub_data_ts.extend_from_slice(recr.data); + tdn.frag_cnt_ts += 1; + SCLogDebug!("stub_data now {}", tdn.stub_data_ts.len()); + } + if dcer.last_frag { + SCLogDebug!("last frag set, so request side of DCERPC closed"); + tx.request_done = true; + } else { + SCLogDebug!("NOT last frag, so request side of DCERPC remains open"); + } + true + }, + None => { + SCLogDebug!("NO previous CMD {} found", dcer.packet_type); + false + }, + }; + return found; + }, + _ => { + state.set_event(SMBEvent::MalformedData); + return false; + }, + } + } + + let tx = state.new_dcerpc_tx(hdr, vercmd, dcer.packet_type, dcer.call_id); + match dcer.packet_type { + DCERPC_TYPE_REQUEST => { + match parse_dcerpc_request_record(dcer.data, dcer.frag_len, dcer.little_endian) { + Ok((_, recr)) => { + SCLogDebug!("DCERPC: REQUEST {:?}", recr); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("first frag size {}", recr.data.len()); + tdn.stub_data_ts.extend_from_slice(recr.data); + tdn.opnum = recr.opnum; + tdn.context_id = recr.context_id; + tdn.frag_cnt_ts += 1; + SCLogDebug!("DCERPC: REQUEST opnum {} stub data len {}", + tdn.opnum, tdn.stub_data_ts.len()); + } + if dcer.last_frag { + tx.request_done = true; + } else { + SCLogDebug!("NOT last frag, so request side of DCERPC remains open"); + } + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + tx.request_done = true; + }, + } + }, + DCERPC_TYPE_BIND => { + let brec = if dcer.little_endian { + parse_dcerpc_bind_record(dcer.data) + } else { + parse_dcerpc_bind_record_big(dcer.data) + }; + match brec { + Ok((_, bindr)) => { + is_bind = true; + SCLogDebug!("SMB DCERPC {:?} BIND {:?}", dcer, bindr); + + if !bindr.ifaces.is_empty() { + let mut ifaces: Vec<DCERPCIface> = Vec::new(); + for i in bindr.ifaces { + let x = if dcer.little_endian { + vec![i.iface[3], i.iface[2], i.iface[1], i.iface[0], + i.iface[5], i.iface[4], i.iface[7], i.iface[6], + i.iface[8], i.iface[9], i.iface[10], i.iface[11], + i.iface[12], i.iface[13], i.iface[14], i.iface[15]] + } else { + i.iface.to_vec() + }; + let uuid_str = uuid::Uuid::from_slice(&x.clone()); + let _uuid_str = uuid_str.map(|uuid_str| uuid_str.to_hyphenated().to_string()).unwrap(); + let d = DCERPCIface::new(x,i.ver,i.ver_min); + SCLogDebug!("UUID {} version {}/{} bytes {:?}", + _uuid_str, + i.ver, i.ver_min,i.iface); + ifaces.push(d); + } + bind_ifaces = Some(ifaces); + } + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + tx.request_done = true; + } + 21..=255 => { + tx.set_event(SMBEvent::MalformedData); + tx.request_done = true; + }, + _ => { + // valid type w/o special processing + tx.request_done = true; + }, + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } + + if is_bind { + // We have to write here the interfaces + // rather than in the BIND block + // due to borrow issues with the tx mutable reference + // that is part of the state + state.dcerpc_ifaces = bind_ifaces; // TODO store per ssn + } + return true; +} + +/// Update TX for bind ack. Needs to update both tx and state. +/// +fn smb_dcerpc_response_bindack( + state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + dcer: &DceRpcRecord, + ntstatus: u32) +{ + match parse_dcerpc_bindack_record(dcer.data) { + Ok((_, bindackr)) => { + SCLogDebug!("SMB READ BINDACK {:?}", bindackr); + + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(DCERPC_TYPE_BINDACK); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + true + }, + None => false, + }; + if found { + if let Some(ref mut ifaces) = state.dcerpc_ifaces { + for (i, r) in bindackr.results.into_iter().enumerate() { + if i >= ifaces.len() { + // TODO set event: more acks that requests + break; + } + ifaces[i].ack_result = r.ack_result; + ifaces[i].acked = true; + } + } + } + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +fn smb_read_dcerpc_record_error(state: &mut SMBState, + hdr: SMBCommonHdr, vercmd: SMBVerCmdStat, ntstatus: u32) + -> bool +{ + let ver = vercmd.get_version(); + let cmd = if ver == 2 { + let (_, c) = vercmd.get_smb2_cmd(); + c + } else { + let (_, c) = vercmd.get_smb1_cmd(); + c as u16 + }; + + let found = match state.get_generic_tx(ver, cmd, &hdr) { + Some(tx) => { + SCLogDebug!("found"); + tx.set_status(ntstatus, false); + tx.response_done = true; + true + }, + None => { + SCLogDebug!("NOT found"); + false + }, + }; + return found; +} + +fn dcerpc_response_handle(tx: &mut SMBTransaction, + vercmd: SMBVerCmdStat, + dcer: &DceRpcRecord) +{ + let (_, ntstatus) = vercmd.get_ntstatus(); + match dcer.packet_type { + DCERPC_TYPE_RESPONSE => { + match parse_dcerpc_response_record(dcer.data, dcer.frag_len) { + Ok((_, respr)) => { + SCLogDebug!("SMBv1 READ RESPONSE {:?}", respr); + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + SCLogDebug!("CMD 11 found at tx {}", tx.id); + tdn.set_result(DCERPC_TYPE_RESPONSE); + tdn.stub_data_tc.extend_from_slice(respr.data); + tdn.frag_cnt_tc += 1; + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = dcer.last_frag; + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + }, + DCERPC_TYPE_BINDACK => { + // handled elsewhere + }, + 21..=255 => { + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + tx.set_event(SMBEvent::MalformedData); + } + _ => { // valid type w/o special processing + if let Some(SMBTransactionTypeData::DCERPC(ref mut tdn)) = tx.type_data { + tdn.set_result(dcer.packet_type); + } + tx.vercmd.set_ntstatus(ntstatus); + tx.response_done = true; + }, + } +} + +/// Handle DCERPC reply record. Called for READ, TRANS, IOCTL +/// +pub fn smb_read_dcerpc_record(state: &mut SMBState, + vercmd: SMBVerCmdStat, + hdr: SMBCommonHdr, + guid: &[u8], + indata: &[u8]) -> bool +{ + let (_, ntstatus) = vercmd.get_ntstatus(); + + if ntstatus != SMB_NTSTATUS_SUCCESS && ntstatus != SMB_NTSTATUS_BUFFER_OVERFLOW { + return smb_read_dcerpc_record_error(state, hdr, vercmd, ntstatus); + } + + SCLogDebug!("lets first see if we have prior data"); + // msg_id 0 as this data crosses cmd/reply pairs + let ehdr = SMBHashKeyHdrGuid::new(SMBCommonHdr::new(SMBHDR_TYPE_TRANS_FRAG, + hdr.ssn_id, hdr.tree_id, 0_u64), guid.to_vec()); + let mut prevdata = match state.ssnguid2vec_map.remove(&ehdr) { + Some(s) => s, + None => Vec::new(), + }; + SCLogDebug!("indata {} prevdata {}", indata.len(), prevdata.len()); + prevdata.extend_from_slice(indata); + let data = prevdata; + + let mut malformed = false; + + if data.is_empty() { + SCLogDebug!("weird: no DCERPC data"); // TODO + // TODO set event? + return false; + + } else { + match parse_dcerpc_record(&data) { + Ok((_, dcer)) => { + SCLogDebug!("DCERPC: version {}.{} read data {} => {:?}", + dcer.version_major, dcer.version_minor, dcer.data.len(), dcer); + + if ntstatus == SMB_NTSTATUS_BUFFER_OVERFLOW && data.len() < dcer.frag_len as usize { + SCLogDebug!("short record {} < {}: storing partial data in state", + data.len(), dcer.frag_len); + state.ssnguid2vec_map.insert(ehdr, data.to_vec()); + return true; // TODO review + } + + if dcer.packet_type == DCERPC_TYPE_BINDACK { + smb_dcerpc_response_bindack(state, vercmd, hdr, &dcer, ntstatus); + return true; + } + + let found = match state.get_dcerpc_tx(&hdr, &vercmd, dcer.call_id) { + Some(tx) => { + dcerpc_response_handle(tx, vercmd.clone(), &dcer); + true + }, + None => { + SCLogDebug!("no tx"); + false + }, + }; + if !found { + // pick up DCERPC tx even if we missed the request + let tx = state.new_dcerpc_tx_for_response(hdr, vercmd.clone(), dcer.call_id); + dcerpc_response_handle(tx, vercmd, &dcer); + } + }, + _ => { + malformed = true; + }, + } + } + + if malformed { + state.set_event(SMBEvent::MalformedData); + } + + return true; +} + +/// Try to find out if the input data looks like DCERPC +pub fn smb_dcerpc_probe(data: &[u8]) -> bool +{ + if let Ok((_, recr)) = parse_dcerpc_record(data) { + SCLogDebug!("SMB: could be DCERPC {:?}", recr); + if recr.version_major == 5 && recr.version_minor < 3 && + recr.frag_len > 0 && recr.packet_type <= 20 + { + SCLogDebug!("SMB: looks like we have dcerpc"); + return true; + } + } + return false; +} diff --git a/rust/src/smb/dcerpc_records.rs b/rust/src/smb/dcerpc_records.rs new file mode 100644 index 0000000..0c8c17f --- /dev/null +++ b/rust/src/smb/dcerpc_records.rs @@ -0,0 +1,247 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::common::nom7::bits; +use crate::smb::error::SmbError; +use nom7::bits::streaming::take as take_bits; +use nom7::bytes::streaming::take; +use nom7::combinator::{cond, rest}; +use nom7::multi::count; +use nom7::number::Endianness; +use nom7::number::streaming::{be_u16, le_u8, le_u16, le_u32, u16, u32}; +use nom7::sequence::tuple; +use nom7::{Err, IResult}; + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcResponseRecord<'a> { + pub data: &'a[u8], +} + +/// parse a packet type 'response' DCERPC record. Implemented +/// as function to be able to pass the fraglen in. +pub fn parse_dcerpc_response_record(i:&[u8], frag_len: u16 ) + -> IResult<&[u8], DceRpcResponseRecord, SmbError> +{ + if frag_len < 24 { + return Err(Err::Error(SmbError::RecordTooSmall)); + } + let (i, _) = take(8_usize)(i)?; + let (i, data) = take(frag_len - 24)(i)?; + let record = DceRpcResponseRecord { data }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcRequestRecord<'a> { + pub opnum: u16, + pub context_id: u16, + pub data: &'a[u8], +} + +/// parse a packet type 'request' DCERPC record. Implemented +/// as function to be able to pass the fraglen in. +pub fn parse_dcerpc_request_record(i:&[u8], frag_len: u16, little: bool) + -> IResult<&[u8], DceRpcRequestRecord, SmbError> +{ + if frag_len < 24 { + return Err(Err::Error(SmbError::RecordTooSmall)); + } + let (i, _) = take(4_usize)(i)?; + let endian = if little { Endianness::Little } else { Endianness::Big }; + let (i, context_id) = u16(endian)(i)?; + let (i, opnum) = u16(endian)(i)?; + let (i, data) = take(frag_len - 24)(i)?; + let record = DceRpcRequestRecord { opnum, context_id, data }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcBindIface<'a> { + pub iface: &'a[u8], + pub ver: u16, + pub ver_min: u16, +} + +pub fn parse_dcerpc_bind_iface(i: &[u8]) -> IResult<&[u8], DceRpcBindIface> { + let (i, _ctx_id) = le_u16(i)?; + let (i, _num_trans_items) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, interface) = take(16_usize)(i)?; + let (i, ver) = le_u16(i)?; + let (i, ver_min) = le_u16(i)?; + let (i, _) = take(20_usize)(i)?; + let res = DceRpcBindIface { + iface:interface, + ver, + ver_min, + }; + Ok((i, res)) +} + +pub fn parse_dcerpc_bind_iface_big(i: &[u8]) -> IResult<&[u8], DceRpcBindIface> { + let (i, _ctx_id) = le_u16(i)?; + let (i, _num_trans_items) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, interface) = take(16_usize)(i)?; + let (i, ver_min) = be_u16(i)?; + let (i, ver) = be_u16(i)?; + let (i, _) = take(20_usize)(i)?; + let res = DceRpcBindIface { + iface:interface, + ver, + ver_min, + }; + Ok((i, res)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcBindRecord<'a> { + pub num_ctx_items: u8, + pub ifaces: Vec<DceRpcBindIface<'a>>, +} + +pub fn parse_dcerpc_bind_record(i: &[u8]) -> IResult<&[u8], DceRpcBindRecord> { + let (i, _max_xmit_frag) = le_u16(i)?; + let (i, _max_recv_frag) = le_u16(i)?; + let (i, _assoc_group) = take(4_usize)(i)?; + let (i, num_ctx_items) = le_u8(i)?; + let (i, _) = take(3_usize)(i)?; // reserved + let (i, ifaces) = count(parse_dcerpc_bind_iface, num_ctx_items as usize)(i)?; + let record = DceRpcBindRecord { + num_ctx_items, + ifaces, + }; + Ok((i, record)) +} + +pub fn parse_dcerpc_bind_record_big(i: &[u8]) -> IResult<&[u8], DceRpcBindRecord> { + let (i, _max_xmit_frag) = be_u16(i)?; + let (i, _max_recv_frag) = be_u16(i)?; + let (i, _assoc_group) = take(4_usize)(i)?; + let (i, num_ctx_items) = le_u8(i)?; + let (i, _) = take(3_usize)(i)?; // reserved + let (i, ifaces) = count(parse_dcerpc_bind_iface_big, num_ctx_items as usize)(i)?; + let record = DceRpcBindRecord { + num_ctx_items, + ifaces, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcBindAckResult<'a> { + pub ack_result: u16, + pub ack_reason: u16, + pub transfer_syntax: &'a[u8], + pub syntax_version: u32, +} + +pub fn parse_dcerpc_bindack_result(i: &[u8]) -> IResult<&[u8], DceRpcBindAckResult> { + let (i, ack_result) = le_u16(i)?; + let (i, ack_reason) = le_u16(i)?; + let (i, transfer_syntax) = take(16_usize)(i)?; + let (i, syntax_version) = le_u32(i)?; + let res = DceRpcBindAckResult { + ack_result, + ack_reason, + transfer_syntax, + syntax_version, + }; + Ok((i, res)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcBindAckRecord<'a> { + pub num_results: u8, + pub results: Vec<DceRpcBindAckResult<'a>>, +} + +pub fn parse_dcerpc_bindack_record(i: &[u8]) -> IResult<&[u8], DceRpcBindAckRecord> { + let (i, _max_xmit_frag) = le_u16(i)?; + let (i, _max_recv_frag) = le_u16(i)?; + let (i, _assoc_group) = take(4_usize)(i)?; + let (i, sec_addr_len) = le_u16(i)?; + let (i, _) = take(sec_addr_len)(i)?; + let (i, _) = cond((sec_addr_len+2) % 4 != 0, take(4 - (sec_addr_len+2) % 4))(i)?; + let (i, num_results) = le_u8(i)?; + let (i, _) = take(3_usize)(i)?; // padding + let (i, results) = count(parse_dcerpc_bindack_result, num_results as usize)(i)?; + let record = DceRpcBindAckRecord { + num_results, + results, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct DceRpcRecord<'a> { + pub version_major: u8, + pub version_minor: u8, + + pub first_frag: bool, + pub last_frag: bool, + + pub frag_len: u16, + + pub little_endian: bool, + + pub packet_type: u8, + + pub call_id: u32, + pub data: &'a[u8], +} + +fn parse_dcerpc_flags1(i:&[u8]) -> IResult<&[u8],(u8,u8,u8)> { + bits(tuple(( + take_bits(6u8), + take_bits(1u8), // last (1) + take_bits(1u8), + )))(i) +} + +fn parse_dcerpc_flags2(i:&[u8]) -> IResult<&[u8],(u32,u32,u32)> { + bits(tuple(( + take_bits(3u32), + take_bits(1u32), // endianness + take_bits(28u32), + )))(i) +} + +pub fn parse_dcerpc_record(i: &[u8]) -> IResult<&[u8], DceRpcRecord> { + let (i, version_major) = le_u8(i)?; + let (i, version_minor) = le_u8(i)?; + let (i, packet_type) = le_u8(i)?; + let (i, packet_flags) = parse_dcerpc_flags1(i)?; + let (i, data_rep) = parse_dcerpc_flags2(i)?; + let endian = if data_rep.1 == 0 { Endianness::Big } else { Endianness::Little }; + let (i, frag_len) = u16(endian)(i)?; + let (i, _auth) = u16(endian)(i)?; + let (i, call_id) = u32(endian)(i)?; + let (i, data) = rest(i)?; + let record = DceRpcRecord { + version_major, + version_minor, + packet_type, + first_frag: packet_flags.2 == 1, + last_frag: packet_flags.1 == 1, + frag_len, + little_endian: data_rep.1 == 1, + call_id, + data, + }; + Ok((i, record)) +} diff --git a/rust/src/smb/debug.rs b/rust/src/smb/debug.rs new file mode 100644 index 0000000..86799dd --- /dev/null +++ b/rust/src/smb/debug.rs @@ -0,0 +1,74 @@ +/* Copyright (C) 2018-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::smb::smb::*; + +impl SMBState { + #[cfg(not(feature = "debug"))] + pub fn _debug_tx_stats(&self) { } + + #[cfg(feature = "debug")] + pub fn _debug_tx_stats(&self) { + if self.transactions.len() > 1 { + let txf = self.transactions.front().unwrap(); + let txl = self.transactions.back().unwrap(); + + SCLogDebug!("TXs {} MIN {} MAX {}", self.transactions.len(), txf.id, txl.id); + SCLogDebug!("- OLD tx.id {}: {:?}", txf.id, txf); + SCLogDebug!("- NEW tx.id {}: {:?}", txl.id, txl); + self._dump_txs(); + } + } + + #[cfg(not(feature = "debug"))] + pub fn _dump_txs(&self) { } + #[cfg(feature = "debug")] + pub fn _dump_txs(&self) { + let len = self.transactions.len(); + for i in 0..len { + let tx = &self.transactions[i]; + let ver = tx.vercmd.get_version(); + let _smbcmd = if ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd + } else { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + cmd as u16 + }; + + match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref d)) => { + SCLogDebug!("idx {} tx id {} progress {}/{} filename {} type_data {:?}", + i, tx.id, tx.request_done, tx.response_done, + String::from_utf8_lossy(&d.file_name), tx.type_data); + }, + _ => { + SCLogDebug!("idx {} tx id {} ver:{} cmd:{} progress {}/{} type_data {:?} tx {:?}", + i, tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data, tx); + }, + } + } + } + + #[cfg(not(feature = "debug"))] + pub fn _debug_state_stats(&self) { } + + #[cfg(feature = "debug")] + pub fn _debug_state_stats(&self) { + SCLogDebug!("ssn2vec_map {} guid2name_map {} ssn2vecoffset_map {} ssn2tree_map {} ssnguid2vec_map {} file_ts_guid {} file_tc_guid {} transactions {}", self.ssn2vec_map.len(), self.guid2name_map.len(), self.ssn2vecoffset_map.len(), self.ssn2tree_map.len(), self.ssnguid2vec_map.len(), self.file_ts_guid.len(), self.file_tc_guid.len(), self.transactions.len()); + } +} diff --git a/rust/src/smb/detect.rs b/rust/src/smb/detect.rs new file mode 100644 index 0000000..c85a6f5 --- /dev/null +++ b/rust/src/smb/detect.rs @@ -0,0 +1,194 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::ptr; +use crate::core::*; +use crate::smb::smb::*; +use crate::dcerpc::detect::{DCEIfaceData, DCEOpnumData, DETECT_DCE_OPNUM_RANGE_UNINITIALIZED}; +use crate::dcerpc::dcerpc::DCERPC_TYPE_REQUEST; +use crate::detect::uint::detect_match_uint; + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_share(tx: &mut SMBTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32) + -> u8 +{ + if let Some(SMBTransactionTypeData::TREECONNECT(ref x)) = tx.type_data { + SCLogDebug!("is_pipe {}", x.is_pipe); + if !x.is_pipe { + *buffer = x.share_name.as_ptr(); + *buffer_len = x.share_name.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_named_pipe(tx: &mut SMBTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32) + -> u8 +{ + if let Some(SMBTransactionTypeData::TREECONNECT(ref x)) = tx.type_data { + SCLogDebug!("is_pipe {}", x.is_pipe); + if x.is_pipe { + *buffer = x.share_name.as_ptr(); + *buffer_len = x.share_name.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_stub_data(tx: &mut SMBTransaction, + direction: u8, + buffer: *mut *const u8, + buffer_len: *mut u32) + -> u8 +{ + if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data { + let vref = if direction == Direction::ToServer as u8 { + &x.stub_data_ts + } else { + &x.stub_data_tc + }; + if !vref.is_empty() { + *buffer = vref.as_ptr(); + *buffer_len = vref.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_smb_tx_match_dce_opnum(tx: &mut SMBTransaction, + dce_data: &mut DCEOpnumData) + -> u8 +{ + SCLogDebug!("rs_smb_tx_get_dce_opnum: start"); + if let Some(SMBTransactionTypeData::DCERPC(ref x)) = tx.type_data { + if x.req_cmd == DCERPC_TYPE_REQUEST { + for range in dce_data.data.iter() { + if range.range2 == DETECT_DCE_OPNUM_RANGE_UNINITIALIZED { + if range.range1 == x.opnum as u32 { + return 1; + } + } else if range.range1 <= x.opnum as u32 && range.range2 >= x.opnum as u32 { + return 1; + } + } + } + } + + return 0; +} + +/* mimic logic that is/was in the C code: + * - match on REQUEST (so not on BIND/BINDACK (probably for mixing with + * dce_opnum and dce_stub_data) + * - only match on approved ifaces (so ack_result == 0) */ +#[no_mangle] +pub extern "C" fn rs_smb_tx_get_dce_iface(state: &mut SMBState, + tx: &mut SMBTransaction, + dce_data: &mut DCEIfaceData) + -> u8 +{ + let if_uuid = dce_data.if_uuid.as_slice(); + let is_dcerpc_request = match tx.type_data { + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + x.req_cmd == DCERPC_TYPE_REQUEST + }, + _ => { false }, + }; + if !is_dcerpc_request { + return 0; + } + let ifaces = match state.dcerpc_ifaces { + Some(ref x) => x, + _ => { + return 0; + }, + }; + + SCLogDebug!("looking for UUID {:?}", if_uuid); + + for i in ifaces { + SCLogDebug!("stored UUID {:?} acked {} ack_result {}", i, i.acked, i.ack_result); + + if i.acked && i.ack_result == 0 && i.uuid == if_uuid { + if let Some(x) = &dce_data.du16 { + if detect_match_uint(x, i.ver) { + return 1; + } + } else { + return 1; + } + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_user(tx: &mut SMBTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32) + -> u8 +{ + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) = tx.type_data { + if let Some(ref ntlmssp) = x.ntlmssp { + *buffer = ntlmssp.user.as_ptr(); + *buffer_len = ntlmssp.user.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_ntlmssp_domain(tx: &mut SMBTransaction, + buffer: *mut *const u8, + buffer_len: *mut u32) + -> u8 +{ + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) = tx.type_data { + if let Some(ref ntlmssp) = x.ntlmssp { + *buffer = ntlmssp.domain.as_ptr(); + *buffer_len = ntlmssp.domain.len() as u32; + return 1; + } + } + + *buffer = ptr::null(); + *buffer_len = 0; + return 0; +} diff --git a/rust/src/smb/error.rs b/rust/src/smb/error.rs new file mode 100644 index 0000000..352c275 --- /dev/null +++ b/rust/src/smb/error.rs @@ -0,0 +1,35 @@ +/* Copyright (C) 2019 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. + */ + +// Author: Pierre Chifflier <chifflier@wzdftpd.net> +use nom7::error::{ErrorKind, ParseError}; + +#[derive(Debug)] +pub enum SmbError { + BadEncoding, + RecordTooSmall, + NomError(ErrorKind), +} + +impl<I> ParseError<I> for SmbError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + SmbError::NomError(kind) + } + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + SmbError::NomError(kind) + } +} diff --git a/rust/src/smb/events.rs b/rust/src/smb/events.rs new file mode 100644 index 0000000..ec79354 --- /dev/null +++ b/rust/src/smb/events.rs @@ -0,0 +1,80 @@ +/* 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. + */ + +use crate::smb::smb::*; + +#[derive(AppLayerEvent)] +pub enum SMBEvent { + InternalError, + MalformedData, + RecordOverflow, + MalformedNtlmsspRequest, + MalformedNtlmsspResponse, + DuplicateNegotiate, + NegotiateMalformedDialects, + FileOverlap, + /// A request was seen in the to client direction. + RequestToClient, + /// A response was seen in the to server direction, + ResponseToServer, + + /// Negotiated max sizes exceed our limit + NegotiateMaxReadSizeTooLarge, + NegotiateMaxWriteSizeTooLarge, + + /// READ request asking for more than `max_read_size` + ReadRequestTooLarge, + /// READ response bigger than `max_read_size` + ReadResponseTooLarge, + ReadQueueSizeExceeded, + ReadQueueCntExceeded, + /// WRITE request for more than `max_write_size` + WriteRequestTooLarge, + WriteQueueSizeExceeded, + WriteQueueCntExceeded, + /// Unusual NTLMSSP fields order + UnusualNtlmsspOrder, + /// Too many live transactions in one flow + TooManyTransactions, +} + +impl SMBTransaction { + /// Set event. + pub fn set_event(&mut self, e: SMBEvent) { + self.tx_data.set_event(e as u8); + } + + /// Set events from vector of events. + pub fn set_events(&mut self, events: Vec<SMBEvent>) { + for e in events { + self.tx_data.set_event(e as u8); + } + } +} + +impl SMBState { + /// Set an event. The event is set on the most recent transaction. + pub fn set_event(&mut self, event: SMBEvent) { + let len = self.transactions.len(); + if len == 0 { + return; + } + + let tx = &mut self.transactions[len - 1]; + tx.set_event(event); + } +} diff --git a/rust/src/smb/files.rs b/rust/src/smb/files.rs new file mode 100644 index 0000000..b290357 --- /dev/null +++ b/rust/src/smb/files.rs @@ -0,0 +1,244 @@ +/* Copyright (C) 2018-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std; +use crate::core::*; +use crate::filetracker::*; +use crate::filecontainer::*; + +use crate::smb::smb::*; + +/// File tracking transaction. Single direction only. +#[derive(Default, Debug)] +pub struct SMBTransactionFile { + pub direction: Direction, + pub fuid: Vec<u8>, + pub file_name: Vec<u8>, + pub share_name: Vec<u8>, + pub file_tracker: FileTransferTracker, + /// after a gap, this will be set to a time in the future. If the file + /// receives no updates before that, it will be considered complete. + pub post_gap_ts: u64, + //pub files: Files, +} + +impl SMBTransactionFile { + 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) }; + } +} + +/// little wrapper around the FileTransferTracker::new_chunk method +pub fn filetracker_newchunk(ft: &mut FileTransferTracker, name: &[u8], data: &[u8], + chunk_offset: u64, chunk_size: u32, is_last: bool, xid: &u32) +{ + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + ft.new_chunk(sfcm, name, data, chunk_offset, + chunk_size, 0, is_last, xid); + } +} + +pub fn filetracker_trunc(ft: &mut FileTransferTracker) +{ + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + ft.trunc(sfcm); + } +} + +pub fn filetracker_close(ft: &mut FileTransferTracker) +{ + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + ft.close(sfcm); + } +} + +fn filetracker_update(ft: &mut FileTransferTracker, data: &[u8], gap_size: u32) -> u32 +{ + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + ft.update(sfcm, data, gap_size) + } else { + 0 + } +} + +impl SMBState { + pub fn new_file_tx(&mut self, fuid: &[u8], file_name: &[u8], direction: Direction) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.type_data = Some(SMBTransactionTypeData::FILE(SMBTransactionFile::new())); + if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data { + d.direction = direction; + d.fuid = fuid.to_vec(); + d.file_name = file_name.to_vec(); + d.file_tracker.tx_id = tx.id - 1; + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + tx.tx_data.init_files_opened(); + tx.tx_data.file_tx = if direction == Direction::ToServer { STREAM_TOSERVER } else { STREAM_TOCLIENT }; // TODO direction to flag func? + SCLogDebug!("SMB: new_file_tx: TX FILE created: ID {} NAME {}", + tx.id, String::from_utf8_lossy(file_name)); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + /// get file tx for a open file. Returns None if a file for the fuid exists, + /// but has already been closed. + pub fn get_file_tx_by_fuid_with_open_file(&mut self, fuid: &[u8], direction: Direction) + -> Option<&mut SMBTransaction> + { + let f = fuid.to_vec(); + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref mut d)) => { + direction == d.direction && f == d.fuid && !d.file_tracker.is_done() + }, + _ => { false }, + }; + + if found { + SCLogDebug!("SMB: Found SMB file TX with ID {}", tx.id); + if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data { + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + return Some(tx); + } + } + SCLogDebug!("SMB: Failed to find SMB TX with FUID {:?}", fuid); + return None; + } + + /// get file tx for a fuid. File may already have been closed. + pub fn get_file_tx_by_fuid(&mut self, fuid: &[u8], direction: Direction) + -> Option<&mut SMBTransaction> + { + let f = fuid.to_vec(); + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::FILE(ref mut d)) => { + direction == d.direction && f == d.fuid + }, + _ => { false }, + }; + + if found { + SCLogDebug!("SMB: Found SMB file TX with ID {}", tx.id); + if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data { + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + return Some(tx); + } + } + SCLogDebug!("SMB: Failed to find SMB TX with FUID {:?}", fuid); + return None; + } + + // update in progress chunks for file transfers + // return how much data we consumed + pub fn filetracker_update(&mut self, direction: Direction, data: &[u8], gap_size: u32) -> u32 { + let mut chunk_left = if direction == Direction::ToServer { + self.file_ts_left + } else { + self.file_tc_left + }; + if chunk_left == 0 { + return 0 + } + SCLogDebug!("chunk_left {} data {}", chunk_left, data.len()); + let file_handle = if direction == Direction::ToServer { + self.file_ts_guid.to_vec() + } else { + self.file_tc_guid.to_vec() + }; + + let data_to_handle_len = if chunk_left as usize >= data.len() { + data.len() + } else { + chunk_left as usize + }; + + if chunk_left <= data.len() as u32 { + chunk_left = 0; + } else { + chunk_left -= data.len() as u32; + } + + if direction == Direction::ToServer { + self.file_ts_left = chunk_left; + } else { + self.file_tc_left = chunk_left; + } + + let ssn_gap = self.ts_ssn_gap | self.tc_ssn_gap; + // get the tx and update it + let consumed = match self.get_file_tx_by_fuid(&file_handle, direction) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if ssn_gap { + let queued_data = tdf.file_tracker.get_queued_size(); + if queued_data > 2000000 { // TODO should probably be configurable + SCLogDebug!("QUEUED size {} while we've seen GAPs. Truncating file.", queued_data); + filetracker_trunc(&mut tdf.file_tracker); + } + } + + // reset timestamp if we get called after a gap + if tdf.post_gap_ts > 0 { + tdf.post_gap_ts = 0; + } + + let file_data = &data[0..data_to_handle_len]; + filetracker_update(&mut tdf.file_tracker, file_data, gap_size) + } else { + 0 + } + }, + None => { + SCLogDebug!("not found for handle {:?}", file_handle); + 0 }, + }; + + return consumed; + } +} + +use crate::applayer::AppLayerGetFileState; +#[no_mangle] +pub unsafe extern "C" fn rs_smb_gettxfiles(_state: *mut std::ffi::c_void, tx: *mut std::ffi::c_void, direction: u8) -> AppLayerGetFileState { + let tx = cast_pointer!(tx, SMBTransaction); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let tx_dir : u8 = tdf.direction.into(); + if direction & tx_dir != 0 { + if let Some(sfcm) = { SURICATA_SMB_FILE_CONFIG } { + return AppLayerGetFileState { fc: &mut tdf.file_tracker.file, cfg: sfcm.files_sbcfg } + } + } + } + AppLayerGetFileState::err() +} diff --git a/rust/src/smb/funcs.rs b/rust/src/smb/funcs.rs new file mode 100644 index 0000000..afab69d --- /dev/null +++ b/rust/src/smb/funcs.rs @@ -0,0 +1,114 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Based on the list in Wiresharks packet-smb2.c +// Names match names from Microsoft. + +pub fn fsctl_func_to_string(f: u32) -> String { + match f { + 0x00060194 => "FSCTL_DFS_GET_REFERRALS", + 0x000601B0 => "FSCTL_DFS_GET_REFERRALS_EX", + 0x00090000 => "FSCTL_REQUEST_OPLOCK_LEVEL_1", + 0x00090004 => "FSCTL_REQUEST_OPLOCK_LEVEL_2", + 0x00090008 => "FSCTL_REQUEST_BATCH_OPLOCK", + 0x0009000C => "FSCTL_OPLOCK_BREAK_ACKNOWLEDGE", + 0x00090010 => "FSCTL_OPBATCH_ACK_CLOSE_PENDING", + 0x00090014 => "FSCTL_OPLOCK_BREAK_NOTIFY", + 0x00090018 => "FSCTL_LOCK_VOLUME", + 0x0009001C => "FSCTL_UNLOCK_VOLUME", + 0x00090020 => "FSCTL_DISMOUNT_VOLUME", + 0x00090028 => "FSCTL_IS_VOLUME_MOUNTED", + 0x0009002C => "FSCTL_IS_PATHNAME_VALID", + 0x00090030 => "FSCTL_MARK_VOLUME_DIRTY", + 0x0009003B => "FSCTL_QUERY_RETRIEVAL_POINTERS", + 0x0009003C => "FSCTL_GET_COMPRESSION", + 0x0009004F => "FSCTL_MARK_AS_SYSTEM_HIVE", + 0x00090050 => "FSCTL_OPLOCK_BREAK_ACK_NO_2", + 0x00090054 => "FSCTL_INVALIDATE_VOLUMES", + 0x00090058 => "FSCTL_QUERY_FAT_BPB", + 0x0009005C => "FSCTL_REQUEST_FILTER_OPLOCK", + 0x00090060 => "FSCTL_FILESYSTEM_GET_STATISTICS", + 0x00090064 => "FSCTL_GET_NTFS_VOLUME_DATA", + 0x00090068 => "FSCTL_GET_NTFS_FILE_RECORD", + 0x0009006F => "FSCTL_GET_VOLUME_BITMAP", + 0x00090073 => "FSCTL_GET_RETRIEVAL_POINTERS", + 0x00090074 => "FSCTL_MOVE_FILE", + 0x00090078 => "FSCTL_IS_VOLUME_DIRTY", + 0x0009007C => "FSCTL_GET_HFS_INFORMATION", + 0x00090083 => "FSCTL_ALLOW_EXTENDED_DASD_IO", + 0x00090087 => "FSCTL_READ_PROPERTY_DATA", + 0x0009008B => "FSCTL_WRITE_PROPERTY_DATA", + 0x0009008F => "FSCTL_FIND_FILES_BY_SID", + 0x00090097 => "FSCTL_DUMP_PROPERTY_DATA", + 0x0009009C => "FSCTL_GET_OBJECT_ID", + 0x000900A4 => "FSCTL_SET_REPARSE_POINT", + 0x000900A8 => "FSCTL_GET_REPARSE_POINT", + 0x000900C0 => "FSCTL_CREATE_OR_GET_OBJECT_ID", + 0x000900C4 => "FSCTL_SET_SPARSE", + 0x000900D4 => "FSCTL_SET_ENCRYPTION", + 0x000900DB => "FSCTL_ENCRYPTION_FSCTL_IO", + 0x000900DF => "FSCTL_WRITE_RAW_ENCRYPTED", + 0x000900E3 => "FSCTL_READ_RAW_ENCRYPTED", + 0x000900F0 => "FSCTL_EXTEND_VOLUME", + 0x00090244 => "FSCTL_CSV_TUNNEL_REQUEST", + 0x0009027C => "FSCTL_GET_INTEGRITY_INFORMATION", + 0x00090284 => "FSCTL_QUERY_FILE_REGIONS", + 0x000902c8 => "FSCTL_CSV_SYNC_TUNNEL_REQUEST", + 0x00090300 => "FSCTL_QUERY_SHARED_VIRTUAL_DISK_SUPPORT", + 0x00090304 => "FSCTL_SVHDX_SYNC_TUNNEL_REQUEST", + 0x00090308 => "FSCTL_SVHDX_SET_INITIATOR_INFORMATION", + 0x0009030C => "FSCTL_SET_EXTERNAL_BACKING", + 0x00090310 => "FSCTL_GET_EXTERNAL_BACKING", + 0x00090314 => "FSCTL_DELETE_EXTERNAL_BACKING", + 0x00090318 => "FSCTL_ENUM_EXTERNAL_BACKING", + 0x0009031F => "FSCTL_ENUM_OVERLAY", + 0x00090350 => "FSCTL_STORAGE_QOS_CONTROL", + 0x00090364 => "FSCTL_SVHDX_ASYNC_TUNNEL_REQUEST", + 0x000940B3 => "FSCTL_ENUM_USN_DATA", + 0x000940B7 => "FSCTL_SECURITY_ID_CHECK", + 0x000940BB => "FSCTL_READ_USN_JOURNAL", + 0x000940CF => "FSCTL_QUERY_ALLOCATED_RANGES", + 0x000940E7 => "FSCTL_CREATE_USN_JOURNAL", + 0x000940EB => "FSCTL_READ_FILE_USN_DATA", + 0x000940EF => "FSCTL_WRITE_USN_CLOSE_RECORD", + 0x00094264 => "FSCTL_OFFLOAD_READ", + 0x00098098 => "FSCTL_SET_OBJECT_ID", + 0x000980A0 => "FSCTL_DELETE_OBJECT_ID", + 0x000980A4 => "FSCTL_SET_REPARSE_POINT", + 0x000980AC => "FSCTL_DELETE_REPARSE_POINT", + 0x000980BC => "FSCTL_SET_OBJECT_ID_EXTENDED", + 0x000980C8 => "FSCTL_SET_ZERO_DATA", + 0x000980D0 => "FSCTL_ENABLE_UPGRADE", + 0x00098208 => "FSCTL_FILE_LEVEL_TRIM", + 0x00098268 => "FSCTL_OFFLOAD_WRITE", + 0x0009C040 => "FSCTL_SET_COMPRESSION", + 0x0009C280 => "FSCTL_SET_INTEGRITY_INFORMATION", + 0x00110018 => "FSCTL_PIPE_WAIT", + 0x0011400C => "FSCTL_PIPE_PEEK", + 0x0011C017 => "FSCTL_PIPE_TRANSCEIVE", + 0x00140078 => "FSCTL_SRV_REQUEST_RESUME_KEY", + 0x001401D4 => "FSCTL_LMR_REQUEST_RESILIENCY", + 0x001401FC => "FSCTL_QUERY_NETWORK_INTERFACE_INFO", + 0x00140200 => "FSCTL_VALIDATE_NEGOTIATE_INFO_224", + 0x00140204 => "FSCTL_VALIDATE_NEGOTIATE_INFO", + 0x00144064 => "FSCTL_SRV_ENUMERATE_SNAPSHOTS", + 0x001440F2 => "FSCTL_SRV_COPYCHUNK", + 0x001441bb => "FSCTL_SRV_READ_HASH", + 0x001480F2 => "FSCTL_SRV_COPYCHUNK_WRITE", + _ => { return (f).to_string(); }, + }.to_string() +} diff --git a/rust/src/smb/log.rs b/rust/src/smb/log.rs new file mode 100644 index 0000000..8496574 --- /dev/null +++ b/rust/src/smb/log.rs @@ -0,0 +1,458 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::str; +use std::string::String; +use uuid; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::smb::smb::*; +use crate::smb::smb1::*; +use crate::smb::smb2::*; +use crate::dcerpc::dcerpc::*; +use crate::smb::funcs::*; +use crate::smb::smb_status::*; + +#[cfg(not(feature = "debug"))] +fn debug_add_progress(_js: &mut JsonBuilder, _tx: &SMBTransaction) -> Result<(), JsonError> { Ok(()) } + +#[cfg(feature = "debug")] +fn debug_add_progress(jsb: &mut JsonBuilder, tx: &SMBTransaction) -> Result<(), JsonError> { + jsb.set_bool("request_done", tx.request_done)?; + jsb.set_bool("response_done", tx.response_done)?; + Ok(()) +} + +/// take in a file GUID (16 bytes) or FID (2 bytes). Also deal +/// with our frankenFID (2 bytes + 4 user_id) +fn fuid_to_string(fuid: &Vec<u8>) -> String { + let fuid_len = fuid.len(); + if fuid_len == 16 { + guid_to_string(fuid) + } else if fuid_len == 2 { + format!("{:02x}{:02x}", fuid[1], fuid[0]) + } else if fuid_len == 6 { + let pure_fid = &fuid[0..2]; + format!("{:02x}{:02x}", pure_fid[1], pure_fid[0]) + } else { + "".to_string() + } +} + +fn guid_to_string(guid: &Vec<u8>) -> String { + if guid.len() == 16 { + let output = format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + guid[3], guid[2], guid[1], guid[0], + guid[5], guid[4], guid[7], guid[6], + guid[9], guid[8], guid[11], guid[10], + guid[15], guid[14], guid[13], guid[12]); + output + } else { + "".to_string() + } +} + +fn smb_common_header(jsb: &mut JsonBuilder, state: &SMBState, tx: &SMBTransaction) -> Result<(), JsonError> +{ + jsb.set_uint("id", tx.id)?; + + if state.dialect != 0 { + let dialect = &smb2_dialect_string(state.dialect); + jsb.set_string("dialect", dialect)?; + } else { + let dialect = match state.dialect_vec { + Some(ref d) => str::from_utf8(d).unwrap_or("invalid"), + None => "unknown", + }; + jsb.set_string("dialect", dialect)?; + } + + match tx.vercmd.get_version() { + 1 => { + let (ok, cmd) = tx.vercmd.get_smb1_cmd(); + if ok { + jsb.set_string("command", &smb1_command_string(cmd))?; + } + }, + 2 => { + let (ok, cmd) = tx.vercmd.get_smb2_cmd(); + if ok { + jsb.set_string("command", &smb2_command_string(cmd))?; + } + }, + _ => { }, + } + + match tx.vercmd.get_ntstatus() { + (true, ntstatus) => { + let status = smb_ntstatus_string(ntstatus); + match status { + Some(x) => jsb.set_string("status", x)?, + None => { + let status_str = format!("{}", ntstatus); + jsb.set_string("status", &status_str)? + }, + }; + let status_hex = format!("0x{:x}", ntstatus); + jsb.set_string("status_code", &status_hex)?; + }, + (false, _) => { + #[allow(clippy::single_match)] + match tx.vercmd.get_dos_error() { + (true, errclass, errcode) => { + match errclass { + 1 => { // DOSERR + let status = smb_dos_error_string(errcode); + jsb.set_string("status", &status)?; + }, + 2 => { // SRVERR + let status = smb_srv_error_string(errcode); + jsb.set_string("status", &status)?; + } + _ => { + let s = format!("UNKNOWN_{:02x}_{:04x}", errclass, errcode); + jsb.set_string("status", &s)?; + }, + } + let status_hex = format!("0x{:04x}", errcode); + jsb.set_string("status_code", &status_hex)?; + }, + (_, _, _) => { + }, + } + }, + } + + + jsb.set_uint("session_id", tx.hdr.ssn_id)?; + jsb.set_uint("tree_id", tx.hdr.tree_id as u64)?; + + debug_add_progress(jsb, tx)?; + + match tx.type_data { + Some(SMBTransactionTypeData::SESSIONSETUP(ref x)) => { + if let Some(ref ntlmssp) = x.ntlmssp { + jsb.open_object("ntlmssp")?; + let domain = String::from_utf8_lossy(&ntlmssp.domain); + jsb.set_string("domain", &domain)?; + + let user = String::from_utf8_lossy(&ntlmssp.user); + jsb.set_string("user", &user)?; + + let host = String::from_utf8_lossy(&ntlmssp.host); + jsb.set_string("host", &host)?; + + if let Some(ref v) = ntlmssp.version { + jsb.set_string("version", v.to_string().as_str())?; + } + + jsb.close()?; + } + + if let Some(ref ticket) = x.krb_ticket { + jsb.open_object("kerberos")?; + jsb.set_string("realm", &ticket.realm.0)?; + jsb.open_array("snames")?; + for sname in ticket.sname.name_string.iter() { + jsb.append_string(sname)?; + } + jsb.close()?; + jsb.close()?; + } + + if let Some(ref r) = x.request_host { + jsb.open_object("request")?; + let os = String::from_utf8_lossy(&r.native_os); + jsb.set_string("native_os", &os)?; + let lm = String::from_utf8_lossy(&r.native_lm); + jsb.set_string("native_lm", &lm)?; + jsb.close()?; + } + if let Some(ref r) = x.response_host { + jsb.open_object("response")?; + let os = String::from_utf8_lossy(&r.native_os); + jsb.set_string("native_os", &os)?; + let lm = String::from_utf8_lossy(&r.native_lm); + jsb.set_string("native_lm", &lm)?; + jsb.close()?; + } + }, + Some(SMBTransactionTypeData::CREATE(ref x)) => { + let mut name_raw = x.filename.to_vec(); + name_raw.retain(|&i|i != 0x00); + if !name_raw.is_empty() { + let name = String::from_utf8_lossy(&name_raw); + if x.directory { + jsb.set_string("directory", &name)?; + } else { + jsb.set_string("filename", &name)?; + } + } else { + // name suggestion from Bro + jsb.set_string("filename", "<share_root>")?; + } + match x.disposition { + 0 => { jsb.set_string("disposition", "FILE_SUPERSEDE")?; }, + 1 => { jsb.set_string("disposition", "FILE_OPEN")?; }, + 2 => { jsb.set_string("disposition", "FILE_CREATE")?; }, + 3 => { jsb.set_string("disposition", "FILE_OPEN_IF")?; }, + 4 => { jsb.set_string("disposition", "FILE_OVERWRITE")?; }, + 5 => { jsb.set_string("disposition", "FILE_OVERWRITE_IF")?; }, + _ => { jsb.set_string("disposition", "UNKNOWN")?; }, + } + if x.delete_on_close { + jsb.set_string("access", "delete on close")?; + } else { + jsb.set_string("access", "normal")?; + } + + // field names inspired by Bro + jsb.set_uint("created", x.create_ts as u64)?; + jsb.set_uint("accessed", x.last_access_ts as u64)?; + jsb.set_uint("modified", x.last_write_ts as u64)?; + jsb.set_uint("changed", x.last_change_ts as u64)?; + jsb.set_uint("size", x.size)?; + + let gs = fuid_to_string(&x.guid); + jsb.set_string("fuid", &gs)?; + }, + Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => { + if x.smb_ver == 1 { + jsb.open_array("client_dialects")?; + for d in &x.dialects { + let dialect = String::from_utf8_lossy(d); + jsb.append_string(&dialect)?; + } + jsb.close()?; + } else if x.smb_ver == 2 { + jsb.open_array("client_dialects")?; + for d in &x.dialects2 { + let dialect = String::from_utf8_lossy(d); + jsb.append_string(&dialect)?; + } + jsb.close()?; + } + + if let Some(ref g) = x.client_guid { + jsb.set_string("client_guid", &guid_to_string(g))?; + } + + jsb.set_string("server_guid", &guid_to_string(&x.server_guid))?; + + if state.max_read_size > 0 { + jsb.set_uint("max_read_size", state.max_read_size.into())?; + } + if state.max_write_size > 0 { + jsb.set_uint("max_write_size", state.max_write_size.into())?; + } + }, + Some(SMBTransactionTypeData::TREECONNECT(ref x)) => { + let share_name = String::from_utf8_lossy(&x.share_name); + if x.is_pipe { + jsb.set_string("named_pipe", &share_name)?; + } else { + jsb.set_string("share", &share_name)?; + } + + // handle services + if tx.vercmd.get_version() == 1 { + jsb.open_object("service")?; + + if let Some(ref s) = x.req_service { + let serv = String::from_utf8_lossy(s); + jsb.set_string("request", &serv)?; + } + if let Some(ref s) = x.res_service { + let serv = String::from_utf8_lossy(s); + jsb.set_string("response", &serv)?; + } + jsb.close()?; + + // share type only for SMB2 + } else { + match x.share_type { + 1 => { jsb.set_string("share_type", "FILE")?; }, + 2 => { jsb.set_string("share_type", "PIPE")?; }, + 3 => { jsb.set_string("share_type", "PRINT")?; }, + _ => { jsb.set_string("share_type", "UNKNOWN")?; }, + } + } + }, + Some(SMBTransactionTypeData::FILE(ref x)) => { + let file_name = String::from_utf8_lossy(&x.file_name); + jsb.set_string("filename", &file_name)?; + let share_name = String::from_utf8_lossy(&x.share_name); + jsb.set_string("share", &share_name)?; + let gs = fuid_to_string(&x.fuid); + jsb.set_string("fuid", &gs)?; + }, + Some(SMBTransactionTypeData::RENAME(ref x)) => { + if tx.vercmd.get_version() == 2 { + jsb.open_object("set_info")?; + jsb.set_string("class", "FILE_INFO")?; + jsb.set_string("info_level", "SMB2_FILE_RENAME_INFO")?; + jsb.close()?; + } + + jsb.open_object("rename")?; + let file_name = String::from_utf8_lossy(&x.oldname); + jsb.set_string("from", &file_name)?; + let file_name = String::from_utf8_lossy(&x.newname); + jsb.set_string("to", &file_name)?; + jsb.close()?; + let gs = fuid_to_string(&x.fuid); + jsb.set_string("fuid", &gs)?; + }, + Some(SMBTransactionTypeData::DCERPC(ref x)) => { + jsb.open_object("dcerpc")?; + if x.req_set { + jsb.set_string("request", &dcerpc_type_string(x.req_cmd))?; + } else { + jsb.set_string("request", "REQUEST_LOST")?; + } + if x.res_set { + jsb.set_string("response", &dcerpc_type_string(x.res_cmd))?; + } else { + jsb.set_string("response", "UNREPLIED")?; + } + if x.req_set { + match x.req_cmd { + DCERPC_TYPE_REQUEST => { + jsb.set_uint("opnum", x.opnum as u64)?; + jsb.open_object("req")?; + jsb.set_uint("frag_cnt", x.frag_cnt_ts as u64)?; + jsb.set_uint("stub_data_size", x.stub_data_ts.len() as u64)?; + jsb.close()?; + if let Some(ref ifaces) = state.dcerpc_ifaces { + // First filter the interfaces to those + // with the context_id we want to log to + // avoid creating an empty "interfaces" + // array. + let mut ifaces = ifaces + .iter() + .filter(|i| i.context_id == x.context_id) + .peekable(); + if ifaces.peek().is_some() { + jsb.open_array("interfaces")?; + for i in ifaces { + jsb.start_object()?; + let ifstr = uuid::Uuid::from_slice(&i.uuid); + let ifstr = ifstr.map(|ifstr| ifstr.to_hyphenated().to_string()).unwrap(); + jsb.set_string("uuid", &ifstr)?; + let vstr = format!("{}.{}", i.ver, i.ver_min); + jsb.set_string("version", &vstr)?; + jsb.close()?; + } + jsb.close()?; + } + } + }, + DCERPC_TYPE_BIND => { + if let Some(ref ifaces) = state.dcerpc_ifaces { + jsb.open_array("interfaces")?; + for i in ifaces { + jsb.start_object()?; + let ifstr = uuid::Uuid::from_slice(&i.uuid); + let ifstr = ifstr.map(|ifstr| ifstr.to_hyphenated().to_string()).unwrap(); + jsb.set_string("uuid", &ifstr)?; + let vstr = format!("{}.{}", i.ver, i.ver_min); + jsb.set_string("version", &vstr)?; + + if i.acked { + jsb.set_uint("ack_result", i.ack_result as u64)?; + jsb.set_uint("ack_reason", i.ack_reason as u64)?; + } + jsb.close()?; + } + jsb.close()?; + } + }, + _ => {}, + } + } + if x.res_set { + #[allow(clippy::single_match)] + match x.res_cmd { + DCERPC_TYPE_RESPONSE => { + jsb.open_object("res")?; + jsb.set_uint("frag_cnt", x.frag_cnt_tc as u64)?; + jsb.set_uint("stub_data_size", x.stub_data_tc.len() as u64)?; + jsb.close()?; + }, + // we don't handle BINDACK w/o BIND + _ => {}, + } + } + jsb.set_uint("call_id", x.call_id as u64)?; + jsb.close()?; + } + Some(SMBTransactionTypeData::IOCTL(ref x)) => { + jsb.set_string("function", &fsctl_func_to_string(x.func))?; + }, + Some(SMBTransactionTypeData::SETFILEPATHINFO(ref x)) => { + let mut name_raw = x.filename.to_vec(); + name_raw.retain(|&i|i != 0x00); + if !name_raw.is_empty() { + let name = String::from_utf8_lossy(&name_raw); + jsb.set_string("filename", &name)?; + } else { + // name suggestion from Bro + jsb.set_string("filename", "<share_root>")?; + } + if x.delete_on_close { + jsb.set_string("access", "delete on close")?; + } else { + jsb.set_string("access", "normal")?; + } + + match x.subcmd { + 8 => { + jsb.set_string("subcmd", "SET_FILE_INFO")?; + }, + 6 => { + jsb.set_string("subcmd", "SET_PATH_INFO")?; + }, + _ => { }, + } + + #[allow(clippy::single_match)] + match x.loi { + 1013 => { // Set Disposition Information + jsb.set_string("level_of_interest", "Set Disposition Information")?; + }, + _ => { }, + } + + let gs = fuid_to_string(&x.fid); + jsb.set_string("fuid", &gs)?; + }, + _ => { }, + } + return Ok(()); +} + +#[no_mangle] +pub extern "C" fn rs_smb_log_json_request(jsb: &mut JsonBuilder, state: &mut SMBState, tx: &mut SMBTransaction) -> bool +{ + smb_common_header(jsb, state, tx).is_ok() +} + +#[no_mangle] +pub extern "C" fn rs_smb_log_json_response(jsb: &mut JsonBuilder, state: &mut SMBState, tx: &mut SMBTransaction) -> bool +{ + smb_common_header(jsb, state, tx).is_ok() +} + diff --git a/rust/src/smb/mod.rs b/rust/src/smb/mod.rs new file mode 100644 index 0000000..5b74f1c --- /dev/null +++ b/rust/src/smb/mod.rs @@ -0,0 +1,47 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! SMB application layer, detection, logger and parser module. + +pub mod error; +pub mod smb_records; +pub mod smb_status; +pub mod smb1_records; +pub mod smb2_records; +pub mod nbss_records; +pub mod dcerpc_records; +pub mod ntlmssp_records; + +pub mod smb; +pub mod smb1; +pub mod smb1_session; +pub mod smb2; +pub mod smb2_session; +pub mod smb2_ioctl; +pub mod smb3; +pub mod dcerpc; +pub mod session; +pub mod log; +pub mod detect; +pub mod debug; +pub mod events; +pub mod auth; +pub mod files; +pub mod funcs; + +//#[cfg(feature = "lua")] +//pub mod lua; diff --git a/rust/src/smb/nbss_records.rs b/rust/src/smb/nbss_records.rs new file mode 100644 index 0000000..6225eb4 --- /dev/null +++ b/rust/src/smb/nbss_records.rs @@ -0,0 +1,223 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::bytes::streaming::take; +use nom7::combinator::rest; +use nom7::number::streaming::be_u32; +use nom7::IResult; + +pub const NBSS_MSGTYPE_SESSION_MESSAGE: u8 = 0x00; +pub const NBSS_MSGTYPE_SESSION_REQUEST: u8 = 0x81; +pub const NBSS_MSGTYPE_POSITIVE_SSN_RESPONSE: u8 = 0x82; +pub const NBSS_MSGTYPE_NEGATIVE_SSN_RESPONSE: u8 = 0x83; +pub const NBSS_MSGTYPE_RETARG_RESPONSE: u8 = 0x84; +pub const NBSS_MSGTYPE_KEEP_ALIVE: u8 = 0x85; + +#[derive(Debug,PartialEq, Eq)] +pub struct NbssRecord<'a> { + pub message_type: u8, + pub length: u32, + pub data: &'a[u8], +} + +impl<'a> NbssRecord<'a> { + pub fn is_valid(&self) -> bool { + let valid = match self.message_type { + NBSS_MSGTYPE_SESSION_MESSAGE | + NBSS_MSGTYPE_SESSION_REQUEST | + NBSS_MSGTYPE_POSITIVE_SSN_RESPONSE | + NBSS_MSGTYPE_NEGATIVE_SSN_RESPONSE | + NBSS_MSGTYPE_RETARG_RESPONSE | + NBSS_MSGTYPE_KEEP_ALIVE => true, + _ => false, + }; + valid + } + pub fn needs_more(&self) -> bool { + return self.is_valid() && self.length >= 4 && self.data.len() < 4; + } + pub fn is_smb(&self) -> bool { + let valid = self.is_valid(); + let smb = self.data.len() >= 4 && + self.data[1] == b'S' && self.data[2] == b'M' && self.data[3] == b'B' && (self.data[0] == b'\xFE' || self.data[0] == b'\xFF' || self.data[0] == b'\xFD'); + + valid && smb + } +} + +pub fn parse_nbss_record(i: &[u8]) -> IResult<&[u8], NbssRecord> { + let (i, buf) = be_u32(i)?; + let message_type = (buf >> 24) as u8; + let length = buf & 0xff_ffff; + let (i, data) = take(length as usize)(i)?; + let record = NbssRecord { + message_type, + length, + data, + }; + Ok((i, record)) +} + +pub fn parse_nbss_record_partial(i: &[u8]) -> IResult<&[u8], NbssRecord> { + let (i, buf) = be_u32(i)?; + let message_type = (buf >> 24) as u8; + let length = buf & 0xff_ffff; + let (i, data) = rest(i)?; + let record = NbssRecord { + message_type, + length, + data, + }; + Ok((i, record)) +} + +#[cfg(test)] +mod tests { + + use super::*; + use nom7::Err; + + #[test] + fn test_parse_nbss_record() { + let buff:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x55, + /* data */ 0xff, 0x53, 0x4d, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x53, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x00, 0x03, + 0x0a, 0x00, 0x01, 0x00, 0x04, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe3, + 0x00, 0x80, 0x2a, 0x55, 0xc4, 0x38, 0x89, 0x03, 0xcd, + 0x01, 0x2c, 0x01, 0x00, 0x10, 0x00, 0xfe, 0x82, 0xf1, + 0x64, 0x0b, 0x66, 0xba, 0x4a, 0xbb, 0x81, 0xe1, 0xea, + 0x54, 0xae, 0xb8, 0x66]; + + let result = parse_nbss_record(buff); + match result { + Ok((remainder, p)) => { + assert_eq!(p.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p.length, 85); + assert_eq!(p.data.len(), 85); + assert_ne!(p.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert!(!p.needs_more()); + + // does this really look like smb? + assert!(p.is_smb()); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + // Non-SMB packet scenario + let buff_not_smb:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x55, + /* data !SMB */ 0xff, 0x52, 0x4e, 0x41, 0x72, 0x00, 0x00, 0x00, 0x00, + 0x98, 0x53, 0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xfe, 0x00, 0x00, 0x00, 0x00, 0x11, 0x05, 0x00, 0x03, + 0x0a, 0x00, 0x01, 0x00, 0x04, 0x11, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xe3, + 0x00, 0x80, 0x2a, 0x55, 0xc4, 0x38, 0x89, 0x03, 0xcd, + 0x01, 0x2c, 0x01, 0x00, 0x10, 0x00, 0xfe, 0x82, 0xf1, + 0x64, 0x0b, 0x66, 0xba, 0x4a, 0xbb, 0x81, 0xe1, 0xea, + 0x54, 0xae, 0xb8, 0x66]; + + let result_not_smb = parse_nbss_record(buff_not_smb); + match result_not_smb { + Ok((remainder, p_not_smb)) => { + assert_eq!(p_not_smb.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p_not_smb.length, 85); + assert_eq!(p_not_smb.data.len(), 85); + assert_ne!(p_not_smb.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert!(!p_not_smb.needs_more()); + + // this packet doesn't have the SMB keyword + // is_smb must be false + assert!(!p_not_smb.is_smb()); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + } + + #[test] + fn test_parse_nbss_record_partial() { + let buff:&[u8] = &[ + /* message type */ 0x00, + /* length */ 0x00, 0x00, 0x29, + /* data < length*/ 0xff, 0x53, 0x4d, 0x42, 0x04, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x43, 0xc8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x08, 0xbd, 0x20, 0x02, 0x08, 0x06, 0x00, + 0x02, 0x40, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00]; + + let result = parse_nbss_record_partial(buff); + match result { + Ok((remainder, p)) => { + assert_eq!(p.message_type, NBSS_MSGTYPE_SESSION_MESSAGE); + assert_eq!(p.length, 41); + assert_ne!(p.data.len(), 41); + assert_ne!(p.message_type, NBSS_MSGTYPE_KEEP_ALIVE); + + // this packet had an acceptable length, we don't need more + assert!(!p.needs_more()); + + // does this really look like smb? + assert!(p.is_smb()); + + // there should be nothing left + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have returned as incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + + } +} diff --git a/rust/src/smb/ntlmssp_records.rs b/rust/src/smb/ntlmssp_records.rs new file mode 100644 index 0000000..d934629 --- /dev/null +++ b/rust/src/smb/ntlmssp_records.rs @@ -0,0 +1,211 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::common::nom7::take_until_and_consume; +use nom7::bytes::streaming::take; +use nom7::combinator::{cond, rest, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::number::streaming::{le_u16, le_u32, le_u8}; +use nom7::Err; +use nom7::IResult; +use std::fmt; + +#[derive(Debug, PartialEq, Eq)] +pub struct NTLMSSPVersion { + pub ver_major: u8, + pub ver_minor: u8, + pub ver_build: u16, + pub ver_ntlm_rev: u8, +} + +impl fmt::Display for NTLMSSPVersion { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "{}.{} build {} rev {}", + self.ver_major, self.ver_minor, self.ver_build, self.ver_ntlm_rev + ) + } +} + +fn parse_ntlm_auth_version(i: &[u8]) -> IResult<&[u8], NTLMSSPVersion> { + let (i, ver_major) = le_u8(i)?; + let (i, ver_minor) = le_u8(i)?; + let (i, ver_build) = le_u16(i)?; + let (i, _) = take(3_usize)(i)?; + let (i, ver_ntlm_rev) = le_u8(i)?; + let version = NTLMSSPVersion { + ver_major, + ver_minor, + ver_build, + ver_ntlm_rev, + }; + Ok((i, version)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct NTLMSSPAuthRecord<'a> { + pub domain: &'a [u8], + pub user: &'a [u8], + pub host: &'a [u8], + pub version: Option<NTLMSSPVersion>, + pub warning: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct NTLMSSPNegotiateFlags { + pub version: bool, + // others fields not done because not interesting yet +} + +fn parse_ntlm_auth_nego_flags(i: &[u8]) -> IResult<&[u8], NTLMSSPNegotiateFlags> { + let (i, raw) = le_u32(i)?; + return Ok(( + i, + NTLMSSPNegotiateFlags { + version: (raw & 0x2000000) != 0, + }, + )); +} + +const NTLMSSP_IDTYPE_LEN: usize = 12; + +fn extract_ntlm_substring(i: &[u8], offset: u32, length: u16) -> IResult<&[u8], &[u8]> { + if offset < NTLMSSP_IDTYPE_LEN as u32 { + return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); + } + let start = offset as usize - NTLMSSP_IDTYPE_LEN; + let end = offset as usize + length as usize - NTLMSSP_IDTYPE_LEN; + if i.len() < end { + return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); + } + return Ok((i, &i[start..end])); +} + +pub fn parse_ntlm_auth_record(i: &[u8]) -> IResult<&[u8], NTLMSSPAuthRecord> { + let orig_i = i; + let record_len = i.len() + NTLMSSP_IDTYPE_LEN; // identifier (8) and type (4) are cut before we are called + + let (i, _lm_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _lm_blob_maxlen) = le_u16(i)?; + let (i, _lm_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, _ntlmresp_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _ntlmresp_blob_maxlen) = le_u16(i)?; + let (i, _ntlmresp_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, domain_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _domain_blob_maxlen) = le_u16(i)?; + let (i, domain_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, user_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _user_blob_maxlen) = le_u16(i)?; + let (i, user_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, host_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _host_blob_maxlen) = le_u16(i)?; + let (i, host_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, _ssnkey_blob_len) = verify(le_u16, |&v| (v as usize) < record_len)(i)?; + let (i, _ssnkey_blob_maxlen) = le_u16(i)?; + let (i, _ssnkey_blob_offset) = verify(le_u32, |&v| (v as usize) < record_len)(i)?; + + let (i, nego_flags) = parse_ntlm_auth_nego_flags(i)?; + let (_, version) = cond(nego_flags.version, parse_ntlm_auth_version)(i)?; + + // Caller does not care about remaining input... + let (_, domain_blob) = extract_ntlm_substring(orig_i, domain_blob_offset, domain_blob_len)?; + let (_, user_blob) = extract_ntlm_substring(orig_i, user_blob_offset, user_blob_len)?; + let (_, host_blob) = extract_ntlm_substring(orig_i, host_blob_offset, host_blob_len)?; + + let mut warning = false; + if (user_blob_offset > 0 && user_blob_offset < domain_blob_offset + domain_blob_len as u32) + || (host_blob_offset > 0 && host_blob_offset < user_blob_offset + user_blob_len as u32) + { + // to set event in transaction + warning = true; + } + + let record = NTLMSSPAuthRecord { + domain: domain_blob, + user: user_blob, + host: host_blob, + warning, + + version, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct NTLMSSPRecord<'a> { + pub msg_type: u32, + pub data: &'a [u8], +} + +pub fn parse_ntlmssp(i: &[u8]) -> IResult<&[u8], NTLMSSPRecord> { + let (i, _) = take_until_and_consume(b"NTLMSSP\x00")(i)?; + let (i, msg_type) = le_u32(i)?; + let (i, data) = rest(i)?; + let record = NTLMSSPRecord { msg_type, data }; + Ok((i, record)) +} + +#[cfg(test)] +mod tests { + use super::*; + use nom7::Err; + #[test] + fn test_parse_auth_nego_flags() { + // ntlmssp.negotiateflags 1 + let blob = [0x15, 0x82, 0x88, 0xe2]; + let result = parse_ntlm_auth_nego_flags(&blob); + match result { + Ok((remainder, flags)) => { + assert!(flags.version); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + // ntlmssp.negotiateflags 0 + let blob = [0x15, 0x82, 0x88, 0xe0]; + let result = parse_ntlm_auth_nego_flags(&blob); + match result { + Ok((remainder, flags)) => { + assert!(!flags.version); + assert_eq!(remainder.len(), 0); + } + Err(Err::Error(err)) => { + panic!("Result should not be an error: {:?}.", err.code); + } + Err(Err::Incomplete(_)) => { + panic!("Result should not have been incomplete."); + } + _ => { + panic!("Unexpected behavior!"); + } + } + } +} diff --git a/rust/src/smb/session.rs b/rust/src/smb/session.rs new file mode 100644 index 0000000..be78669 --- /dev/null +++ b/rust/src/smb/session.rs @@ -0,0 +1,69 @@ +/* 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. + */ + +use crate::kerberos::*; +use crate::smb::smb::*; +use crate::smb::smb1_session::*; +use crate::smb::auth::*; + +#[derive(Default, Debug)] +pub struct SMBTransactionSessionSetup { + pub request_host: Option<SessionSetupRequest>, + pub response_host: Option<SessionSetupResponse>, + pub ntlmssp: Option<NtlmsspData>, + pub krb_ticket: Option<Kerberos5Ticket>, +} + +impl SMBTransactionSessionSetup { + pub fn new() -> Self { + return Default::default() + } +} + +impl SMBState { + pub fn new_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::SESSIONSETUP( + SMBTransactionSessionSetup::new())); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SESSIONSETUP created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_sessionsetup_tx(&mut self, hdr: SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let hit = tx.hdr.compare(&hdr) && match tx.type_data { + Some(SMBTransactionTypeData::SESSIONSETUP(_)) => { true }, + _ => { false }, + }; + if hit { + return Some(tx); + } + } + return None; + } +} diff --git a/rust/src/smb/smb.rs b/rust/src/smb/smb.rs new file mode 100644 index 0000000..d6b0a56 --- /dev/null +++ b/rust/src/smb/smb.rs @@ -0,0 +1,2435 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* TODO + * - check all parsers for calls on non-SUCCESS status + */ + +/* GAP processing: + * - if post-gap we've seen a succesful tx req/res: we consider "re-sync'd" + */ + +// written by Victor Julien + +use std; +use std::str; +use std::ffi::{self, CString}; + +use std::collections::HashMap; +use std::collections::VecDeque; + +use nom7::{Err, Needed}; +use nom7::error::{make_error, ErrorKind}; + +use crate::core::*; +use crate::applayer; +use crate::applayer::*; +use crate::frames::*; +use crate::conf::*; +use crate::applayer::{AppLayerResult, AppLayerTxData, AppLayerEvent}; + +use crate::smb::nbss_records::*; +use crate::smb::smb1_records::*; +use crate::smb::smb2_records::*; + +use crate::smb::smb1::*; +use crate::smb::smb2::*; +use crate::smb::smb3::*; +use crate::smb::dcerpc::*; +use crate::smb::session::*; +use crate::smb::events::*; +use crate::smb::files::*; +use crate::smb::smb2_ioctl::*; + +#[derive(AppLayerFrameType)] +pub enum SMBFrameType { + NBSSPdu, + NBSSHdr, + NBSSData, + SMB1Pdu, + SMB1Hdr, + SMB1Data, + SMB2Pdu, + SMB2Hdr, + SMB2Data, + SMB3Pdu, + SMB3Hdr, + SMB3Data, +} + +pub const MIN_REC_SIZE: u16 = 32 + 4; // SMB hdr + nbss hdr +pub const SMB_CONFIG_DEFAULT_STREAM_DEPTH: u32 = 0; + +pub static mut SMB_CFG_MAX_READ_SIZE: u32 = 16777216; +pub static mut SMB_CFG_MAX_READ_QUEUE_SIZE: u32 = 67108864; +pub static mut SMB_CFG_MAX_READ_QUEUE_CNT: u32 = 64; +pub static mut SMB_CFG_MAX_WRITE_SIZE: u32 = 16777216; +pub static mut SMB_CFG_MAX_WRITE_QUEUE_SIZE: u32 = 67108864; +pub static mut SMB_CFG_MAX_WRITE_QUEUE_CNT: u32 = 64; + +static mut ALPROTO_SMB: AppProto = ALPROTO_UNKNOWN; + +static mut SMB_MAX_TX: usize = 1024; + +pub static mut SURICATA_SMB_FILE_CONFIG: Option<&'static SuricataFileContext> = None; + +#[no_mangle] +pub extern "C" fn rs_smb_init(context: &'static mut SuricataFileContext) +{ + unsafe { + SURICATA_SMB_FILE_CONFIG = Some(context); + } +} + +pub const SMB_SRV_ERROR: u16 = 1; +pub const SMB_SRV_BADPW: u16 = 2; +pub const SMB_SRV_BADTYPE: u16 = 3; +pub const SMB_SRV_ACCESS: u16 = 4; +pub const SMB_SRV_BADUID: u16 = 91; + +pub fn smb_srv_error_string(c: u16) -> String { + match c { + SMB_SRV_ERROR => "SRV_ERROR", + SMB_SRV_BADPW => "SRV_BADPW", + SMB_SRV_BADTYPE => "SRV_BADTYPE", + SMB_SRV_ACCESS => "SRV_ACCESS", + SMB_SRV_BADUID => "SRV_BADUID", + _ => { return (c).to_string(); }, + }.to_string() +} + +pub const SMB_DOS_SUCCESS: u16 = 0; +pub const SMB_DOS_BAD_FUNC: u16 = 1; +pub const SMB_DOS_BAD_FILE: u16 = 2; +pub const SMB_DOS_BAD_PATH: u16 = 3; +pub const SMB_DOS_TOO_MANY_OPEN_FILES: u16 = 4; +pub const SMB_DOS_ACCESS_DENIED: u16 = 5; + +pub fn smb_dos_error_string(c: u16) -> String { + match c { + SMB_DOS_SUCCESS => "DOS_SUCCESS", + SMB_DOS_BAD_FUNC => "DOS_BAD_FUNC", + SMB_DOS_BAD_FILE => "DOS_BAD_FILE", + SMB_DOS_BAD_PATH => "DOS_BAD_PATH", + SMB_DOS_TOO_MANY_OPEN_FILES => "DOS_TOO_MANY_OPEN_FILES", + SMB_DOS_ACCESS_DENIED => "DOS_ACCESS_DENIED", + _ => { return (c).to_string(); }, + }.to_string() +} + +pub const NTLMSSP_NEGOTIATE: u32 = 1; +#[cfg(feature = "debug")] +pub const NTLMSSP_CHALLENGE: u32 = 2; +pub const NTLMSSP_AUTH: u32 = 3; + +#[cfg(feature = "debug")] +pub fn ntlmssp_type_string(c: u32) -> String { + match c { + NTLMSSP_NEGOTIATE => "NTLMSSP_NEGOTIATE", + NTLMSSP_CHALLENGE => "NTLMSSP_CHALLENGE", + NTLMSSP_AUTH => "NTLMSSP_AUTH", + _ => { return (c).to_string(); }, + }.to_string() +} + +#[derive(Default, Eq, PartialEq, Debug, Clone)] +pub struct SMBVerCmdStat { + smb_ver: u8, + smb1_cmd: u8, + smb2_cmd: u16, + + status_set: bool, + status_is_dos_error: bool, + status_error_class: u8, + status: u32, +} + +impl SMBVerCmdStat { + pub fn new() -> Self { + Default::default() + } + pub fn new1(cmd: u8) -> Self { + return Self { + smb_ver: 1, + smb1_cmd: cmd, + ..Default::default() + } + } + pub fn new1_with_ntstatus(cmd: u8, status: u32) -> Self { + return Self { + smb_ver: 1, + smb1_cmd: cmd, + status_set: true, + status, + ..Default::default() + } + } + pub fn new2(cmd: u16) -> Self { + return Self { + smb_ver: 2, + smb2_cmd: cmd, + ..Default::default() + } + } + + pub fn new2_with_ntstatus(cmd: u16, status: u32) -> Self { + return Self { + smb_ver: 2, + smb2_cmd: cmd, + status_set: true, + status, + ..Default::default() + } + } + + pub fn set_smb1_cmd(&mut self, cmd: u8) -> bool { + if self.smb_ver != 0 { + return false; + } + self.smb_ver = 1; + self.smb1_cmd = cmd; + return true; + } + + pub fn set_smb2_cmd(&mut self, cmd: u16) -> bool { + if self.smb_ver != 0 { + return false; + } + self.smb_ver = 2; + self.smb2_cmd = cmd; + return true; + } + + pub fn get_version(&self) -> u8 { + self.smb_ver + } + + pub fn get_smb1_cmd(&self) -> (bool, u8) { + if self.smb_ver != 1 { + return (false, 0); + } + return (true, self.smb1_cmd); + } + + pub fn get_smb2_cmd(&self) -> (bool, u16) { + if self.smb_ver != 2 { + return (false, 0); + } + return (true, self.smb2_cmd); + } + + pub fn get_ntstatus(&self) -> (bool, u32) { + (self.status_set && !self.status_is_dos_error, self.status) + } + + pub fn get_dos_error(&self) -> (bool, u8, u16) { + (self.status_set && self.status_is_dos_error, self.status_error_class, self.status as u16) + } + + fn set_status(&mut self, status: u32, is_dos_error: bool) + { + if is_dos_error { + self.status_is_dos_error = true; + self.status_error_class = (status & 0x0000_00ff) as u8; + self.status = (status & 0xffff_0000) >> 16; + } else { + self.status = status; + } + self.status_set = true; + } + + pub fn set_ntstatus(&mut self, status: u32) + { + self.set_status(status, false) + } + + pub fn set_status_dos_error(&mut self, status: u32) + { + self.set_status(status, true) + } +} + +/// "The FILETIME structure is a 64-bit value that represents the number of +/// 100-nanosecond intervals that have elapsed since January 1, 1601, +/// Coordinated Universal Time (UTC)." +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct SMBFiletime { + ts: u64, +} + +impl SMBFiletime { + pub fn new(raw: u64) -> Self { + Self { + ts: raw, + } + } + + /// inspired by Bro, convert FILETIME to secs since unix epoch + pub fn as_unix(&self) -> u32 { + if self.ts > 116_444_736_000_000_000_u64 { + let ts = self.ts / 10000000 - 11644473600; + ts as u32 + } else { + 0 + } + } +} + +#[derive(Debug)] +pub enum SMBTransactionTypeData { + FILE(SMBTransactionFile), + TREECONNECT(SMBTransactionTreeConnect), + NEGOTIATE(SMBTransactionNegotiate), + DCERPC(SMBTransactionDCERPC), + CREATE(SMBTransactionCreate), + SESSIONSETUP(SMBTransactionSessionSetup), + IOCTL(SMBTransactionIoctl), + RENAME(SMBTransactionRename), + SETFILEPATHINFO(SMBTransactionSetFilePathInfo), +} + +// Used for Trans2 SET_PATH_INFO and SET_FILE_INFO +#[derive(Debug)] +pub struct SMBTransactionSetFilePathInfo { + pub subcmd: u16, + pub loi: u16, + pub delete_on_close: bool, + pub filename: Vec<u8>, + pub fid: Vec<u8>, +} + +impl SMBTransactionSetFilePathInfo { + pub fn new(filename: Vec<u8>, fid: Vec<u8>, subcmd: u16, loi: u16, delete_on_close: bool) + -> Self + { + return Self { + filename, fid, + subcmd, + loi, + delete_on_close, + }; + } +} + +impl SMBState { + pub fn new_setfileinfo_tx(&mut self, filename: Vec<u8>, fid: Vec<u8>, + subcmd: u16, loi: u16, delete_on_close: bool) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO( + SMBTransactionSetFilePathInfo::new( + filename, fid, subcmd, loi, delete_on_close))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn new_setpathinfo_tx(&mut self, filename: Vec<u8>, + subcmd: u16, loi: u16, delete_on_close: bool) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + let fid : Vec<u8> = Vec::new(); + tx.type_data = Some(SMBTransactionTypeData::SETFILEPATHINFO( + SMBTransactionSetFilePathInfo::new(filename, fid, + subcmd, loi, delete_on_close))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX SETFILEPATHINFO created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } +} + +#[derive(Debug)] +pub struct SMBTransactionRename { + pub oldname: Vec<u8>, + pub newname: Vec<u8>, + pub fuid: Vec<u8>, +} + +impl SMBTransactionRename { + pub fn new(fuid: Vec<u8>, oldname: Vec<u8>, newname: Vec<u8>) -> Self { + return Self { + fuid, oldname, newname, + }; + } +} + +impl SMBState { + pub fn new_rename_tx(&mut self, fuid: Vec<u8>, oldname: Vec<u8>, newname: Vec<u8>) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.type_data = Some(SMBTransactionTypeData::RENAME( + SMBTransactionRename::new(fuid, oldname, newname))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX RENAME created: ID {}", tx.id); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionCreate { + pub disposition: u32, + pub delete_on_close: bool, + pub directory: bool, + pub filename: Vec<u8>, + pub guid: Vec<u8>, + + pub create_ts: u32, + pub last_access_ts: u32, + pub last_write_ts: u32, + pub last_change_ts: u32, + + pub size: u64, +} + +impl SMBTransactionCreate { + pub fn new(filename: Vec<u8>, disp: u32, del: bool, dir: bool) -> Self { + return Self { + disposition: disp, + delete_on_close: del, + directory: dir, + filename, + ..Default::default() + } + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionNegotiate { + pub smb_ver: u8, + pub dialects: Vec<Vec<u8>>, + pub dialects2: Vec<Vec<u8>>, + + // SMB1 doesn't have the client GUID + pub client_guid: Option<Vec<u8>>, + pub server_guid: Vec<u8>, +} + +impl SMBTransactionNegotiate { + pub fn new(smb_ver: u8) -> Self { + return Self { + smb_ver, + server_guid: Vec::with_capacity(16), + ..Default::default() + }; + } +} + +#[derive(Default, Debug)] +pub struct SMBTransactionTreeConnect { + pub is_pipe: bool, + pub share_type: u8, + pub tree_id: u32, + pub share_name: Vec<u8>, + + /// SMB1 service strings + pub req_service: Option<Vec<u8>>, + pub res_service: Option<Vec<u8>>, +} + +impl SMBTransactionTreeConnect { + pub fn new(share_name: Vec<u8>) -> Self { + return Self { + share_name, + ..Default::default() + }; + } +} + +#[derive(Debug)] +pub struct SMBTransaction { + pub id: u64, /// internal id + + /// version, command and status + pub vercmd: SMBVerCmdStat, + /// session id, tree id, etc. + pub hdr: SMBCommonHdr, + + /// for state tracking. false means this side is in progress, true + /// that it's complete. + pub request_done: bool, + pub response_done: bool, + + /// Command specific data + pub type_data: Option<SMBTransactionTypeData>, + + pub tx_data: AppLayerTxData, +} + +impl Transaction for SMBTransaction { + fn id(&self) -> u64 { + self.id + } +} + +impl Default for SMBTransaction { + fn default() -> Self { + Self::new() + } +} + +impl SMBTransaction { + pub fn new() -> Self { + return Self { + id: 0, + vercmd: SMBVerCmdStat::new(), + hdr: SMBCommonHdr::default(), + request_done: false, + response_done: false, + type_data: None, + tx_data: AppLayerTxData::new(), + } + } + + pub fn set_status(&mut self, status: u32, is_dos_error: bool) + { + if is_dos_error { + self.vercmd.set_status_dos_error(status); + } else { + self.vercmd.set_ntstatus(status); + } + } + + pub fn free(&mut self) { + SCLogDebug!("SMB TX {:p} free ID {}", &self, self.id); + debug_validate_bug_on!(self.tx_data.files_opened > 1); + debug_validate_bug_on!(self.tx_data.files_logged > 1); + } +} + +impl Drop for SMBTransaction { + fn drop(&mut self) { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = self.type_data { + if let Some(sfcm) = unsafe { SURICATA_SMB_FILE_CONFIG } { + tdf.file_tracker.file.free(sfcm); + } + } + self.free(); + } +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +pub struct SMBFileGUIDOffset { + pub guid: Vec<u8>, + pub offset: u64, +} + +impl SMBFileGUIDOffset { + pub fn new(guid: Vec<u8>, offset: u64) -> Self { + Self { + guid, + offset, + } + } +} + +/// type values to make sure we're not mixing things +/// up in hashmap lookups +pub const SMBHDR_TYPE_GUID: u32 = 1; +pub const SMBHDR_TYPE_SHARE: u32 = 2; +pub const SMBHDR_TYPE_FILENAME: u32 = 3; +pub const SMBHDR_TYPE_OFFSET: u32 = 4; +pub const SMBHDR_TYPE_GENERICTX: u32 = 5; +pub const SMBHDR_TYPE_HEADER: u32 = 6; +//unused pub const SMBHDR_TYPE_MAX_SIZE: u32 = 7; // max resp size for SMB1_COMMAND_TRANS +pub const SMBHDR_TYPE_TRANS_FRAG: u32 = 8; +pub const SMBHDR_TYPE_TREE: u32 = 9; +pub const SMBHDR_TYPE_DCERPCTX: u32 = 10; + +#[derive(Default, Hash, Eq, PartialEq, Debug)] +pub struct SMBCommonHdr { + pub ssn_id: u64, + pub tree_id: u32, + pub rec_type: u32, + pub msg_id: u64, +} + +impl SMBCommonHdr { + pub fn new(rec_type: u32, ssn_id: u64, tree_id: u32, msg_id: u64) -> Self { + Self { + rec_type, + ssn_id, + tree_id, + msg_id, + } + } + pub fn from2(r: &Smb2Record, rec_type: u32) -> SMBCommonHdr { + let tree_id = match rec_type { + SMBHDR_TYPE_TREE => { 0 }, + _ => r.tree_id, + }; + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.message_id }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.session_id, + tree_id, + msg_id, + } + + } + pub fn from2_notree(r: &Smb2Record, rec_type: u32) -> SMBCommonHdr { + // async responses do not have a tree id (even if the request has it) + // making thus the match between the two impossible. + // Per spec, MessageId should be enough to identify a message request and response uniquely + // across all messages that are sent on the same SMB2 Protocol transport connection. + // cf https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/ea4560b7-90da-4803-82b5-344754b92a79 + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.message_id }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.session_id, + tree_id : 0, + msg_id, + } + } + pub fn from1(r: &SmbRecord, rec_type: u32) -> SMBCommonHdr { + let tree_id = match rec_type { + SMBHDR_TYPE_TREE => { 0 }, + _ => r.tree_id as u32, + }; + let msg_id = match rec_type { + SMBHDR_TYPE_TRANS_FRAG | SMBHDR_TYPE_SHARE => { 0 }, + _ => { r.multiplex_id as u64 }, + }; + + SMBCommonHdr { + rec_type, + ssn_id : r.ssn_id as u64, + tree_id, + msg_id, + } + } + + // don't include tree id + pub fn compare(&self, hdr: &SMBCommonHdr) -> bool { + self.rec_type == hdr.rec_type && self.ssn_id == hdr.ssn_id && + self.msg_id == hdr.msg_id + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBHashKeyHdrGuid { + hdr: SMBCommonHdr, + guid: Vec<u8>, +} + +impl SMBHashKeyHdrGuid { + pub fn new(hdr: SMBCommonHdr, guid: Vec<u8>) -> Self { + Self { + hdr, guid, + } + } +} + +#[derive(Hash, Eq, PartialEq, Debug)] +pub struct SMBTree { + pub name: Vec<u8>, + pub is_pipe: bool, +} + +impl SMBTree { + pub fn new(name: Vec<u8>, is_pipe: bool) -> Self { + Self { + name, + is_pipe, + } + } +} + +pub fn u32_as_bytes(i: u32) -> [u8;4] { + let o1: u8 = ((i >> 24) & 0xff) as u8; + let o2: u8 = ((i >> 16) & 0xff) as u8; + let o3: u8 = ((i >> 8) & 0xff) as u8; + let o4: u8 = (i & 0xff) as u8; + return [o1, o2, o3, o4] +} + +#[derive(Default, Debug)] +pub struct SMBState<> { + pub state_data: AppLayerStateData, + + /// map ssn/tree/msgid to vec (guid/name/share) + pub ssn2vec_map: HashMap<SMBCommonHdr, Vec<u8>>, + /// map guid to filename + pub guid2name_map: HashMap<Vec<u8>, Vec<u8>>, + /// map ssn key to read offset + pub ssn2vecoffset_map: HashMap<SMBCommonHdr, SMBFileGUIDOffset>, + + pub ssn2tree_map: HashMap<SMBCommonHdr, SMBTree>, + + // store partial data records that are transferred in multiple + // requests for DCERPC. + pub ssnguid2vec_map: HashMap<SMBHashKeyHdrGuid, Vec<u8>>, + + skip_ts: u32, + skip_tc: u32, + + pub file_ts_left : u32, + pub file_tc_left : u32, + pub file_ts_guid : Vec<u8>, + pub file_tc_guid : Vec<u8>, + + pub ts_ssn_gap: bool, + pub tc_ssn_gap: bool, + + pub ts_gap: bool, // last TS update was gap + pub tc_gap: bool, // last TC update was gap + + pub ts_trunc: bool, // no more data for TOSERVER + pub tc_trunc: bool, // no more data for TOCLIENT + + /// true as long as we have file txs that are in a post-gap + /// state. It means we'll do extra house keeping for those. + check_post_gap_file_txs: bool, + post_gap_files_checked: bool, + + /// transactions list + pub transactions: VecDeque<SMBTransaction>, + tx_index_completed: usize, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, + + /// SMB2 dialect or 0 if not set or SMB1 + pub dialect: u16, + /// contains name of SMB1 dialect + pub dialect_vec: Option<Vec<u8>>, // used if dialect == 0 + + /// dcerpc interfaces, stored here to be able to match + /// them while inspecting DCERPC REQUEST txs + pub dcerpc_ifaces: Option<Vec<DCERPCIface>>, + + pub max_read_size: u32, + pub max_write_size: u32, + + /// Timestamp in seconds of last update. This is packet time, + /// potentially coming from pcaps. + ts: u64, +} + +impl State<SMBTransaction> for SMBState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&SMBTransaction> { + self.transactions.get(index) + } +} + +impl SMBState { + /// Allocation function for a new TLS parser instance + pub fn new() -> Self { + Self { + state_data:AppLayerStateData::new(), + ssn2vec_map:HashMap::new(), + guid2name_map:HashMap::new(), + ssn2vecoffset_map:HashMap::new(), + ssn2tree_map:HashMap::new(), + ssnguid2vec_map:HashMap::new(), + skip_ts:0, + skip_tc:0, + file_ts_left:0, + file_tc_left:0, + file_ts_guid:Vec::new(), + file_tc_guid:Vec::new(), + ts_ssn_gap: false, + tc_ssn_gap: false, + ts_gap: false, + tc_gap: false, + ts_trunc: false, + tc_trunc: false, + check_post_gap_file_txs: false, + post_gap_files_checked: false, + transactions: VecDeque::new(), + tx_index_completed: 0, + tx_id:0, + dialect:0, + dialect_vec: None, + dcerpc_ifaces: None, + ts: 0, + ..Default::default() + } + } + + pub fn free(&mut self) { + //self._debug_state_stats(); + self._debug_tx_stats(); + } + + pub fn new_tx(&mut self) -> SMBTransaction { + let mut tx = SMBTransaction::new(); + self.tx_id += 1; + tx.id = self.tx_id; + SCLogDebug!("TX {} created", tx.id); + if self.transactions.len() > unsafe { SMB_MAX_TX } { + let mut index = self.tx_index_completed; + for tx_old in &mut self.transactions.range_mut(self.tx_index_completed..) { + index += 1; + if !tx_old.request_done || !tx_old.response_done { + tx_old.request_done = true; + tx_old.response_done = true; + tx_old.set_event(SMBEvent::TooManyTransactions); + break; + } + } + self.tx_index_completed = index; + + } + return tx; + } + + pub fn free_tx(&mut self, tx_id: u64) { + SCLogDebug!("Freeing TX with ID {} TX.ID {}", tx_id, tx_id+1); + 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; + SCLogDebug!("tx {} progress {}/{}", tx.id, tx.request_done, tx.response_done); + break; + } + } + if found { + SCLogDebug!("freeing TX with ID {} TX.ID {} at index {} left: {} max id: {}", + tx_id, tx_id+1, index, self.transactions.len(), self.tx_id); + self.tx_index_completed = 0; + self.transactions.remove(index); + } + } + + pub fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SMBTransaction> { +/* + if self.transactions.len() > 100 { + SCLogNotice!("get_tx_by_id: tx_id={} in list={}", tx_id, self.transactions.len()); + self._dump_txs(); + panic!("txs exploded"); + } +*/ + for tx in &mut self.transactions { + if tx.id == tx_id + 1 { + let ver = tx.vercmd.get_version(); + let mut _smbcmd; + if ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + _smbcmd = cmd; + } else { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + _smbcmd = cmd as u16; + } + SCLogDebug!("Found SMB TX: id {} ver:{} cmd:{} progress {}/{} type_data {:?}", + tx.id, ver, _smbcmd, tx.request_done, tx.response_done, tx.type_data); + /* hack: apply flow file flags to file tx here to make sure its propagated */ + if let Some(SMBTransactionTypeData::FILE(ref mut d)) = tx.type_data { + tx.tx_data.update_file_flags(self.state_data.file_flags); + d.update_file_flags(tx.tx_data.file_flags); + } + return Some(tx); + } + } + SCLogDebug!("Failed to find SMB TX with ID {}", tx_id); + return None; + } + + fn update_ts(&mut self, ts: u64) { + if ts != self.ts { + self.ts = ts; + self.post_gap_files_checked = false; + } + } + + /* generic TX has no type_data and is only used to + * track a single cmd request/reply pair. */ + + pub fn new_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: SMBCommonHdr) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + if smb_ver == 1 && smb_cmd <= 255 { + tx.vercmd.set_smb1_cmd(smb_cmd as u8); + } else if smb_ver == 2 { + tx.vercmd.set_smb2_cmd(smb_cmd); + } + + tx.type_data = None; + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + tx.hdr = key; + + SCLogDebug!("SMB: TX GENERIC created: ID {} tx list {} {:?}", + tx.id, self.transactions.len(), &tx); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_last_tx(&mut self, smb_ver: u8, smb_cmd: u16) + -> Option<&mut SMBTransaction> + { + let tx_ref = self.transactions.back_mut(); + if let Some(tx) = tx_ref { + let found = if tx.vercmd.get_version() == smb_ver { + if smb_ver == 1 { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + cmd as u16 == smb_cmd + } else if smb_ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd == smb_cmd + } else { + false + } + } else { + false + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn get_generic_tx(&mut self, smb_ver: u8, smb_cmd: u16, key: &SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let found = if tx.vercmd.get_version() == smb_ver { + if smb_ver == 1 { + let (_, cmd) = tx.vercmd.get_smb1_cmd(); + cmd as u16 == smb_cmd && tx.hdr.compare(key) + } else if smb_ver == 2 { + let (_, cmd) = tx.vercmd.get_smb2_cmd(); + cmd == smb_cmd && tx.hdr.compare(key) + } else { + false + } + } else { + false + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn new_negotiate_tx(&mut self, smb_ver: u8) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + if smb_ver == 1 { + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_NEGOTIATE_PROTOCOL); + } else if smb_ver == 2 { + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_NEGOTIATE_PROTOCOL); + } + + tx.type_data = Some(SMBTransactionTypeData::NEGOTIATE( + SMBTransactionNegotiate::new(smb_ver))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX NEGOTIATE created: ID {} SMB ver {}", tx.id, smb_ver); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_negotiate_tx(&mut self, smb_ver: u8) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let found = match tx.type_data { + Some(SMBTransactionTypeData::NEGOTIATE(ref x)) => { + x.smb_ver == smb_ver + }, + _ => { false }, + }; + if found { + return Some(tx); + } + } + return None; + } + + pub fn new_treeconnect_tx(&mut self, hdr: SMBCommonHdr, name: Vec<u8>) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::TREECONNECT( + SMBTransactionTreeConnect::new(name.to_vec()))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX TREECONNECT created: ID {} NAME {}", + tx.id, String::from_utf8_lossy(&name)); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_treeconnect_tx(&mut self, hdr: SMBCommonHdr) + -> Option<&mut SMBTransaction> + { + for tx in &mut self.transactions { + let hit = tx.hdr.compare(&hdr) && match tx.type_data { + Some(SMBTransactionTypeData::TREECONNECT(_)) => { true }, + _ => { false }, + }; + if hit { + return Some(tx); + } + } + return None; + } + + pub fn new_create_tx(&mut self, file_name: &[u8], + disposition: u32, del: bool, dir: bool, + hdr: SMBCommonHdr) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::CREATE( + SMBTransactionCreate::new( + file_name.to_vec(), disposition, + del, dir))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } + + pub fn get_service_for_guid(&self, guid: &[u8]) -> (&'static str, bool) + { + let (name, is_dcerpc) = match self.guid2name_map.get(&guid.to_vec()) { + Some(n) => { + let mut s = n.as_slice(); + // skip leading \ if we have it + if s.len() > 1 && s[0] == 0x5c_u8 { + s = &s[1..]; + } + match str::from_utf8(s) { + Ok("PSEXESVC") => ("PSEXESVC", false), + Ok("svcctl") => ("svcctl", true), + Ok("srvsvc") => ("srvsvc", true), + Ok("atsvc") => ("atsvc", true), + Ok("lsarpc") => ("lsarpc", true), + Ok("samr") => ("samr", true), + Ok("spoolss") => ("spoolss", true), + Ok("winreg") => ("winreg", true), + Ok("suricata::dcerpc") => ("unknown", true), + Err(_) => ("MALFORMED", false), + Ok(&_) => { + SCLogDebug!("don't know {}", String::from_utf8_lossy(n)); + ("UNKNOWN", false) + }, + } + }, + _ => { ("UNKNOWN", false) }, + }; + SCLogDebug!("service {} is_dcerpc {}", name, is_dcerpc); + (name, is_dcerpc) + } + + fn post_gap_housekeeping_for_files(&mut self) + { + let mut post_gap_txs = false; + for tx in &mut self.transactions { + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + if f.post_gap_ts > 0 { + if self.ts > f.post_gap_ts { + tx.request_done = true; + tx.response_done = true; + filetracker_trunc(&mut f.file_tracker); + } else { + post_gap_txs = true; + } + } + } + } + self.check_post_gap_file_txs = post_gap_txs; + } + + /* after a gap we will consider all transactions complete for our + * direction. File transfer transactions are an exception. Those + * can handle gaps. For the file transactions we set the current + * (flow) time and prune them in 60 seconds if no update for them + * was received. */ + fn post_gap_housekeeping(&mut self, dir: Direction) + { + if self.ts_ssn_gap && dir == Direction::ToServer { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TS", tx.id); + tx.request_done = true; + } + } + } else if self.tc_ssn_gap && dir == Direction::ToClient { + for tx in &mut self.transactions { + if tx.id >= self.tx_id { + SCLogDebug!("post_gap_housekeeping: done"); + break; + } + if let Some(SMBTransactionTypeData::FILE(ref mut f)) = tx.type_data { + // leaving FILE txs open as they can deal with gaps. We + // remove them after 60 seconds of no activity though. + if f.post_gap_ts == 0 { + f.post_gap_ts = self.ts + 60; + self.check_post_gap_file_txs = true; + } + } else { + SCLogDebug!("post_gap_housekeeping: tx {} marked as done TC", tx.id); + tx.request_done = true; + tx.response_done = true; + } + } + + } + } + + pub fn set_file_left(&mut self, direction: Direction, rec_size: u32, data_size: u32, fuid: Vec<u8>) + { + let left = rec_size.saturating_sub(data_size); + if direction == Direction::ToServer { + self.file_ts_left = left; + self.file_ts_guid = fuid; + } else { + self.file_tc_left = left; + self.file_tc_guid = fuid; + } + } + + pub fn set_skip(&mut self, direction: Direction, nbss_remaining: u32) + { + if direction == Direction::ToServer { + self.skip_ts = nbss_remaining; + } else { + self.skip_tc = nbss_remaining; + } + } + + // return how much data we consumed + fn handle_skip(&mut self, direction: Direction, input_size: u32) -> u32 { + let mut skip_left = if direction == Direction::ToServer { + self.skip_ts + } else { + self.skip_tc + }; + if skip_left == 0 { + return 0 + } + SCLogDebug!("skip_left {} input_size {}", skip_left, input_size); + + let consumed = if skip_left >= input_size { + input_size + } else { + skip_left + }; + + if skip_left <= input_size { + skip_left = 0; + } else { + skip_left -= input_size; + } + + if direction == Direction::ToServer { + self.skip_ts = skip_left; + } else { + self.skip_tc = skip_left; + } + return consumed; + } + + fn add_nbss_ts_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> (Option<Frame>, Option<Frame>, Option<Frame>) { + let nbss_pdu = Frame::new(flow, stream_slice, input, nbss_len + 4, SMBFrameType::NBSSPdu as u8); + SCLogDebug!("NBSS PDU frame {:?}", nbss_pdu); + let nbss_hdr_frame = Frame::new(flow, stream_slice, input, 4_i64, SMBFrameType::NBSSHdr as u8); + SCLogDebug!("NBSS HDR frame {:?}", nbss_hdr_frame); + let nbss_data_frame = Frame::new(flow, stream_slice, &input[4..], nbss_len, SMBFrameType::NBSSData as u8); + SCLogDebug!("NBSS DATA frame {:?}", nbss_data_frame); + (nbss_pdu, nbss_hdr_frame, nbss_data_frame) + } + + fn add_smb1_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB1Pdu as u8); + SCLogDebug!("SMB PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb1_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb1_hdr = Frame::new(flow, stream_slice, input, 32_i64, SMBFrameType::SMB1Hdr as u8); + SCLogDebug!("SMBv1 HDR frame {:?}", _smb1_hdr); + if input.len() > 32 { + let _smb1_data = Frame::new(flow, stream_slice, &input[32..], nbss_len - 32, SMBFrameType::SMB1Data as u8); + SCLogDebug!("SMBv1 DATA frame {:?}", _smb1_data); + } + } + + fn add_smb2_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB2Pdu as u8); + SCLogDebug!("SMBv2 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb2_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64, hdr_len: i64) { + let _smb2_hdr = Frame::new(flow, stream_slice, input, hdr_len, SMBFrameType::SMB2Hdr as u8); + SCLogDebug!("SMBv2 HDR frame {:?}", _smb2_hdr); + if input.len() > hdr_len as usize { + let _smb2_data = Frame::new(flow, stream_slice, &input[hdr_len as usize..], nbss_len - hdr_len, SMBFrameType::SMB2Data as u8); + SCLogDebug!("SMBv2 DATA frame {:?}", _smb2_data); + } + } + + fn add_smb3_ts_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB3Pdu as u8); + SCLogDebug!("SMBv3 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb3_ts_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb3_hdr = Frame::new(flow, stream_slice, input, 52_i64, SMBFrameType::SMB3Hdr as u8); + SCLogDebug!("SMBv3 HDR frame {:?}", _smb3_hdr); + if input.len() > 52 { + let _smb3_data = Frame::new(flow, stream_slice, &input[52..], nbss_len - 52, SMBFrameType::SMB3Data as u8); + SCLogDebug!("SMBv3 DATA frame {:?}", _smb3_data); + } + } + + /// return bytes consumed + pub fn parse_tcp_data_ts_partial(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + // check for malformed data. Wireshark reports as + // 'NBSS continuation data'. If it's invalid we're + // lost so we give up. + if input.len() > 8 { + if let Ok((_, ref hdr)) = parse_nbss_record_partial(input) { + if !hdr.is_smb() { + SCLogDebug!("partial NBSS, not SMB and no known msg type {}", hdr.message_type); + self.trunc_ts(); + return 0; + } + } + } + return 0; + } + + if let Ok((output, ref nbss_part_hdr)) = parse_nbss_record_partial(input) { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + if let Ok((_, ref smb)) = parse_smb_version(nbss_part_hdr.data) { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref r)) = parse_smb_record(nbss_part_hdr.data) { + if r.command == SMB1_COMMAND_WRITE_ANDX { + // see if it's a write to a pipe. We only handle those + // if complete. + let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE, + r.ssn_id as u64, r.tree_id as u32, 0); + let is_pipe = match self.ssn2tree_map.get(&tree_key) { + Some(n) => n.is_pipe, + None => false, + }; + if is_pipe { + return 0; + } + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb1_write_request_record(self, r, SMB1_HEADER_SIZE, SMB1_COMMAND_WRITE_ANDX, nbss_remaining); + + self.add_nbss_ts_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb1_ts_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb1_ts_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + + let consumed = input.len() - output.len(); + return consumed; + } + } + } else if smb.version == 0xfe_u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_request_record(nbss_part_hdr.data) { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_WRITE { + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb2_write_request_record(self, smb_record, nbss_remaining); + + self.add_nbss_ts_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb2_ts_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb2_ts_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64, smb_record.header_len as i64); + + let consumed = input.len() - output.len(); + SCLogDebug!("consumed {}", consumed); + return consumed; + } + } + } + // no SMB3 here yet, will buffer full records + } + } + } + + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_ts(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult + { + let mut cur_i = stream_slice.as_slice(); + let consumed = self.handle_skip(Direction::ToServer, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToServer, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + // gap + if self.ts_gap { + SCLogDebug!("TS trying to catch up after GAP (input {})", cur_i.len()); + while !cur_i.is_empty() { // min record size + match search_smb_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.len(); + if smb2_offset < 4 { + cur_i = &cur_i[smb2_offset+4..]; + continue; // see if we have another record in our data + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.ts_gap = false; + break; + }, + _ => { + let mut consumed = stream_slice.len(); + if consumed < 4 { + consumed = 0; + } else { + consumed -= 3; + } + SCLogDebug!("smb record NOT found"); + return AppLayerResult::incomplete(consumed, 8); + }, + } + } + } + while !cur_i.is_empty() { // min record size + match parse_nbss_record(cur_i) { + Ok((rem, ref nbss_hdr)) => { + SCLogDebug!("nbss frame offset {} len {}", stream_slice.offset_from(cur_i), cur_i.len() - rem.len()); + let (_, _, nbss_data_frame) = self.add_nbss_ts_frames(flow, stream_slice, cur_i, nbss_hdr.length as i64); + + if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + // we have the full records size worth of data, + // let's parse it + match parse_smb_version(nbss_hdr.data) { + Ok((_, ref smb)) => { + + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + + SCLogDebug!("SMBv1 record"); + match parse_smb_record(nbss_hdr.data) { + Ok((_, ref smb_record)) => { + let pdu_frame = self.add_smb1_ts_pdu_frame(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + self.add_smb1_ts_hdr_data_frames(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + if smb_record.is_request() { + smb1_request_record(self, smb_record); + } else { + // If we received a response when expecting a request, set an event + // on the PDU frame instead of handling the response. + SCLogDebug!("SMB1 reply seen from client to server"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::ResponseToServer as u8); + } + } + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else if smb.version == 0xfe_u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv2 record"); + match parse_smb2_request_record(nbss_data) { + Ok((nbss_data_rem, ref smb_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + let pdu_frame = self.add_smb2_ts_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb2_ts_hdr_data_frames(flow, stream_slice, nbss_data, record_len, smb_record.header_len as i64); + SCLogDebug!("nbss_data_rem {}", nbss_data_rem.len()); + if smb_record.is_request() { + smb2_request_record(self, smb_record); + } else { + // If we received a response when expecting a request, set an event + // on the PDU frame instead of handling the response. + SCLogDebug!("SMB2 reply seen from client to server"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::ResponseToServer as u8); + } + } + nbss_data = nbss_data_rem; + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } else if smb.version == 0xfd_u8 { // SMB3 transform + + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv3 transform record"); + match parse_smb3_transform_record(nbss_data) { + Ok((nbss_data_rem, ref _smb3_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + self.add_smb3_ts_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb3_ts_hdr_data_frames(flow, stream_slice, nbss_data, record_len); + nbss_data = nbss_data_rem; + }, + _ => { + if let Some(frame) = nbss_data_frame { + frame.add_event(flow, SMBEvent::MalformedData as u8); + } + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + Err(Err::Incomplete(needed)) => { + if let Needed::Size(n) = needed { + let n = usize::from(n) + cur_i.len(); + // 512 is the minimum for parse_tcp_data_ts_partial + if n >= 512 && cur_i.len() < 512 { + let total_consumed = stream_slice.offset_from(cur_i); + return AppLayerResult::incomplete(total_consumed, 512); + } + let consumed = self.parse_tcp_data_ts_partial(flow, stream_slice, cur_i); + if consumed == 0 { + // if we consumed none we will buffer the entire record + let total_consumed = stream_slice.offset_from(cur_i); + SCLogDebug!("setting consumed {} need {} needed {:?} total input {}", + total_consumed, n, needed, stream_slice.len()); + let need = n; + return AppLayerResult::incomplete(total_consumed, need as u32); + } + // tracking a write record, which we don't need to + // queue up at the stream level, but can feed to us + // in small chunks + return AppLayerResult::ok(); + } else { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + 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 add_nbss_tc_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> (Option<Frame>, Option<Frame>, Option<Frame>) { + let nbss_pdu = Frame::new(flow, stream_slice, input, nbss_len + 4, SMBFrameType::NBSSPdu as u8); + SCLogDebug!("NBSS PDU frame {:?}", nbss_pdu); + let nbss_hdr_frame = Frame::new(flow, stream_slice, input, 4_i64, SMBFrameType::NBSSHdr as u8); + SCLogDebug!("NBSS HDR frame {:?}", nbss_hdr_frame); + let nbss_data_frame = Frame::new(flow, stream_slice, &input[4..], nbss_len, SMBFrameType::NBSSData as u8); + SCLogDebug!("NBSS DATA frame {:?}", nbss_data_frame); + (nbss_pdu, nbss_hdr_frame, nbss_data_frame) + } + + fn add_smb1_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB1Pdu as u8); + SCLogDebug!("SMB PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb1_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb1_hdr = Frame::new(flow, stream_slice, input, SMB1_HEADER_SIZE as i64, SMBFrameType::SMB1Hdr as u8); + SCLogDebug!("SMBv1 HDR frame {:?}", _smb1_hdr); + if input.len() > SMB1_HEADER_SIZE { + let _smb1_data = Frame::new(flow, stream_slice, &input[SMB1_HEADER_SIZE..], nbss_len - SMB1_HEADER_SIZE as i64, + SMBFrameType::SMB1Data as u8); + SCLogDebug!("SMBv1 DATA frame {:?}", _smb1_data); + } + } + + fn add_smb2_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) -> Option<Frame> { + let smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB2Pdu as u8); + SCLogDebug!("SMBv2 PDU frame {:?}", smb_pdu); + smb_pdu + } + fn add_smb2_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64, hdr_len: i64) { + let _smb2_hdr = Frame::new(flow, stream_slice, input, hdr_len, SMBFrameType::SMB2Hdr as u8); + SCLogDebug!("SMBv2 HDR frame {:?}", _smb2_hdr); + if input.len() > hdr_len as usize { + let _smb2_data = Frame::new(flow, stream_slice, &input[hdr_len as usize ..], nbss_len - hdr_len, SMBFrameType::SMB2Data as u8); + SCLogDebug!("SMBv2 DATA frame {:?}", _smb2_data); + } + } + + fn add_smb3_tc_pdu_frame(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb_pdu = Frame::new(flow, stream_slice, input, nbss_len, SMBFrameType::SMB3Pdu as u8); + SCLogDebug!("SMBv3 PDU frame {:?}", _smb_pdu); + } + fn add_smb3_tc_hdr_data_frames(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], nbss_len: i64) { + let _smb3_hdr = Frame::new(flow, stream_slice, input, 52_i64, SMBFrameType::SMB3Hdr as u8); + SCLogDebug!("SMBv3 HDR frame {:?}", _smb3_hdr); + if input.len() > 52 { + let _smb3_data = Frame::new(flow, stream_slice, &input[52..], nbss_len - 52, SMBFrameType::SMB3Data as u8); + SCLogDebug!("SMBv3 DATA frame {:?}", _smb3_data); + } + } + + /// return bytes consumed + pub fn parse_tcp_data_tc_partial(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> usize + { + SCLogDebug!("incomplete of size {}", input.len()); + if input.len() < 512 { + // check for malformed data. Wireshark reports as + // 'NBSS continuation data'. If it's invalid we're + // lost so we give up. + if input.len() > 8 { + if let Ok((_, ref hdr)) = parse_nbss_record_partial(input) { + if !hdr.is_smb() { + SCLogDebug!("partial NBSS, not SMB and no known msg type {}", hdr.message_type); + self.trunc_tc(); + return 0; + } + } + } + return 0; + } + + if let Ok((output, ref nbss_part_hdr)) = parse_nbss_record_partial(input) { + SCLogDebug!("parse_nbss_record_partial ok, output len {}", output.len()); + if nbss_part_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + if let Ok((_, ref smb)) = parse_smb_version(nbss_part_hdr.data) { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 255u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref r)) = parse_smb_record(nbss_part_hdr.data) { + SCLogDebug!("SMB1: partial record {}", + r.command); + if r.command == SMB1_COMMAND_READ_ANDX { + let tree_key = SMBCommonHdr::new(SMBHDR_TYPE_SHARE, + r.ssn_id as u64, r.tree_id as u32, 0); + let is_pipe = match self.ssn2tree_map.get(&tree_key) { + Some(n) => n.is_pipe, + None => false, + }; + if is_pipe { + return 0; + } + + // create NBSS frames here so we don't get double frames + // when we don't consume the data now. + self.add_nbss_tc_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb1_tc_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb1_tc_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb1_read_response_record(self, r, SMB1_HEADER_SIZE, nbss_remaining); + let consumed = input.len() - output.len(); + return consumed; + } + } + } else if smb.version == 254u8 { // SMB2 + SCLogDebug!("SMBv2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_response_record(nbss_part_hdr.data) { + SCLogDebug!("SMB2: partial record {}", + &smb2_command_string(smb_record.command)); + if smb_record.command == SMB2_COMMAND_READ { + // create NBSS frames here so we don't get double frames + // when we don't consume the data now. + self.add_nbss_tc_frames(flow, stream_slice, input, nbss_part_hdr.length as i64); + self.add_smb2_tc_pdu_frame(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64); + self.add_smb2_tc_hdr_data_frames(flow, stream_slice, nbss_part_hdr.data, nbss_part_hdr.length as i64, smb_record.header_len as i64); + + // how many more bytes are expected within this NBSS record + // So that we can check that further parsed offsets and lengths + // stay within the NBSS record. + let nbss_remaining = nbss_part_hdr.length - nbss_part_hdr.data.len() as u32; + smb2_read_response_record(self, smb_record, nbss_remaining); + let consumed = input.len() - output.len(); + return consumed; + } + } + } + // no SMB3 here yet, will buffer full records + } + } + } + return 0; + } + + /// Parsing function, handling TCP chunks fragmentation + pub fn parse_tcp_data_tc(&mut self, flow: *const Flow, stream_slice: &StreamSlice) -> AppLayerResult + { + let mut cur_i = stream_slice.as_slice(); + let consumed = self.handle_skip(Direction::ToClient, cur_i.len() as u32); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + // take care of in progress file chunk transfers + // and skip buffer beyond it + let consumed = self.filetracker_update(Direction::ToClient, cur_i, 0); + if consumed > 0 { + if consumed > cur_i.len() as u32 { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + cur_i = &cur_i[consumed as usize..]; + } + if cur_i.is_empty() { + return AppLayerResult::ok(); + } + // gap + if self.tc_gap { + SCLogDebug!("TC trying to catch up after GAP (input {})", cur_i.len()); + while !cur_i.is_empty() { // min record size + match search_smb_record(cur_i) { + Ok((_, pg)) => { + SCLogDebug!("smb record found"); + let smb2_offset = cur_i.len() - pg.len(); + if smb2_offset < 4 { + cur_i = &cur_i[smb2_offset+4..]; + continue; // see if we have another record in our data + } + let nbss_offset = smb2_offset - 4; + cur_i = &cur_i[nbss_offset..]; + + self.tc_gap = false; + break; + }, + _ => { + let mut consumed = stream_slice.len(); + if consumed < 4 { + consumed = 0; + } else { + consumed -= 3; + } + SCLogDebug!("smb record NOT found"); + return AppLayerResult::incomplete(consumed, 8); + }, + } + } + } + while !cur_i.is_empty() { // min record size + match parse_nbss_record(cur_i) { + Ok((rem, ref nbss_hdr)) => { + SCLogDebug!("nbss record offset {} len {}", stream_slice.offset_from(cur_i), cur_i.len() - rem.len()); + self.add_nbss_tc_frames(flow, stream_slice, cur_i, nbss_hdr.length as i64); + SCLogDebug!("nbss frames added"); + + if nbss_hdr.message_type == NBSS_MSGTYPE_SESSION_MESSAGE { + // we have the full records size worth of data, + // let's parse it + match parse_smb_version(nbss_hdr.data) { + Ok((_, ref smb)) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + match parse_smb_record(nbss_hdr.data) { + Ok((_, ref smb_record)) => { + let pdu_frame = self.add_smb1_tc_pdu_frame(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + self.add_smb1_tc_hdr_data_frames(flow, stream_slice, nbss_hdr.data, nbss_hdr.length as i64); + if smb_record.is_response() { + smb1_response_record(self, smb_record); + } else { + SCLogDebug!("SMB1 request seen from server to client"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::RequestToClient as u8); + } + } + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else if smb.version == 0xfe_u8 { // SMB2 + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv2 record"); + match parse_smb2_response_record(nbss_data) { + Ok((nbss_data_rem, ref smb_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + let pdu_frame = self.add_smb2_tc_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb2_tc_hdr_data_frames(flow, stream_slice, nbss_data, record_len, smb_record.header_len as i64); + if smb_record.is_response() { + smb2_response_record(self, smb_record); + } else { + SCLogDebug!("SMB2 request seen from server to client"); + if let Some(frame) = pdu_frame { + frame.add_event(flow, SMBEvent::RequestToClient as u8); + } + } + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } else if smb.version == 0xfd_u8 { // SMB3 transform + let mut nbss_data = nbss_hdr.data; + while !nbss_data.is_empty() { + SCLogDebug!("SMBv3 transform record"); + match parse_smb3_transform_record(nbss_data) { + Ok((nbss_data_rem, ref _smb3_record)) => { + let record_len = (nbss_data.len() - nbss_data_rem.len()) as i64; + self.add_smb3_tc_pdu_frame(flow, stream_slice, nbss_data, record_len); + self.add_smb3_tc_hdr_data_frames(flow, stream_slice, nbss_data, record_len); + nbss_data = nbss_data_rem; + }, + _ => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } + } + }, + Err(Err::Incomplete(_)) => { + // not enough data to contain basic SMB hdr + // TODO event: empty NBSS_MSGTYPE_SESSION_MESSAGE + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + return AppLayerResult::err(); + }, + } + } else { + SCLogDebug!("NBSS message {:X}", nbss_hdr.message_type); + } + cur_i = rem; + }, + Err(Err::Incomplete(needed)) => { + SCLogDebug!("INCOMPLETE have {} needed {:?}", cur_i.len(), needed); + if let Needed::Size(n) = needed { + let n = usize::from(n) + cur_i.len(); + // 512 is the minimum for parse_tcp_data_tc_partial + if n >= 512 && cur_i.len() < 512 { + let total_consumed = stream_slice.offset_from(cur_i); + return AppLayerResult::incomplete(total_consumed, 512); + } + let consumed = self.parse_tcp_data_tc_partial(flow, stream_slice, cur_i); + if consumed == 0 { + // if we consumed none we will buffer the entire record + let total_consumed = stream_slice.offset_from(cur_i); + SCLogDebug!("setting consumed {} need {} needed {:?} total input {}", + total_consumed, n, needed, stream_slice.len()); + let need = n; + return AppLayerResult::incomplete(total_consumed, need as u32); + } + // tracking a read record, which we don't need to + // queue up at the stream level, but can feed to us + // in small chunks + return AppLayerResult::ok(); + } else { + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + }, + Err(_) => { + self.set_event(SMBEvent::MalformedData); + 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; + } + self._debug_tx_stats(); + AppLayerResult::ok() + } + + /// handle a gap in the TOSERVER direction + /// returns: 0 ok, 1 unrecoverable error + pub fn parse_tcp_data_ts_gap(&mut self, gap_size: u32) -> AppLayerResult { + let consumed = self.handle_skip(Direction::ToServer, gap_size); + if consumed < gap_size { + let new_gap_size = gap_size - consumed; + let gap = vec![0; new_gap_size as usize]; + + let consumed2 = self.filetracker_update(Direction::ToServer, &gap, new_gap_size); + if consumed2 > new_gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size); + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + } + SCLogDebug!("GAP of size {} in toserver direction", gap_size); + self.ts_ssn_gap = true; + self.ts_gap = true; + return AppLayerResult::ok(); + } + + /// handle a gap in the TOCLIENT direction + /// returns: 0 ok, 1 unrecoverable error + pub fn parse_tcp_data_tc_gap(&mut self, gap_size: u32) -> AppLayerResult { + let consumed = self.handle_skip(Direction::ToClient, gap_size); + if consumed < gap_size { + let new_gap_size = gap_size - consumed; + let gap = vec![0; new_gap_size as usize]; + + let consumed2 = self.filetracker_update(Direction::ToClient, &gap, new_gap_size); + if consumed2 > new_gap_size { + SCLogDebug!("consumed more than GAP size: {} > {}", consumed2, new_gap_size); + self.set_event(SMBEvent::InternalError); + return AppLayerResult::err(); + } + } + SCLogDebug!("GAP of size {} in toclient direction", gap_size); + self.tc_ssn_gap = true; + self.tc_gap = true; + return AppLayerResult::ok(); + } + + pub fn trunc_ts(&mut self) { + SCLogDebug!("TRUNC TS"); + self.ts_trunc = true; + + for tx in &mut self.transactions { + if !tx.request_done { + SCLogDebug!("TRUNCATING TX {} in TOSERVER direction", tx.id); + tx.request_done = true; + } + } + } + pub fn trunc_tc(&mut self) { + SCLogDebug!("TRUNC TC"); + self.tc_trunc = true; + + for tx in &mut self.transactions { + if !tx.response_done { + SCLogDebug!("TRUNCATING TX {} in TOCLIENT direction", tx.id); + tx.response_done = true; + } + } + } +} + +/// Returns *mut SMBState +#[no_mangle] +pub extern "C" fn rs_smb_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = SMBState::new(); + let boxed = Box::new(state); + SCLogDebug!("allocating state"); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut SMBState as void pointer +#[no_mangle] +pub extern "C" fn rs_smb_state_free(state: *mut std::os::raw::c_void) { + SCLogDebug!("freeing state"); + let mut smb_state = unsafe { Box::from_raw(state as *mut SMBState) }; + smb_state.free(); +} + +/// C binding parse a SMB request. Returns 1 on success, -1 on failure. +#[no_mangle] +pub unsafe extern "C" fn rs_smb_parse_request_tcp(flow: *const Flow, + state: *mut ffi::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) + -> AppLayerResult +{ + let state = cast_pointer!(state, SMBState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_smb_parse_request_tcp_gap(state, stream_slice.gap_size()); + } + + SCLogDebug!("parsing {} bytes of request data", stream_slice.len()); + + /* START with MISTREAM set: record might be starting the middle. */ + if stream_slice.flags() & (STREAM_START|STREAM_MIDSTREAM) == (STREAM_START|STREAM_MIDSTREAM) { + state.ts_gap = true; + } + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_ts(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_request_tcp_gap( + state: &mut SMBState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_ts_gap(input_len) +} + + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_parse_response_tcp(flow: *const Flow, + state: *mut ffi::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const ffi::c_void, + ) + -> AppLayerResult +{ + let state = cast_pointer!(state, SMBState); + let flow = cast_pointer!(flow, Flow); + + if stream_slice.is_gap() { + return rs_smb_parse_response_tcp_gap(state, stream_slice.gap_size()); + } + + /* START with MISTREAM set: record might be starting the middle. */ + if stream_slice.flags() & (STREAM_START|STREAM_MIDSTREAM) == (STREAM_START|STREAM_MIDSTREAM) { + state.tc_gap = true; + } + + state.update_ts(flow.get_last_time().as_secs()); + state.parse_tcp_data_tc(flow, &stream_slice) +} + +#[no_mangle] +pub extern "C" fn rs_smb_parse_response_tcp_gap( + state: &mut SMBState, + input_len: u32) + -> AppLayerResult +{ + state.parse_tcp_data_tc_gap(input_len) +} + +fn smb_probe_tcp_midstream(direction: Direction, slice: &[u8], rdir: *mut u8, begins: bool) -> i8 +{ + let r = if begins { + // if pattern was found in the beginning, just check first byte + if slice[0] == NBSS_MSGTYPE_SESSION_MESSAGE { + Ok((&slice[..4], &slice[4..])) + } else { + Err(Err::Error(make_error(slice, ErrorKind::Eof))) + } + } else { + search_smb_record(slice) + }; + match r { + Ok((_, data)) => { + SCLogDebug!("smb found"); + match parse_smb_version(data) { + Ok((_, ref smb)) => { + SCLogDebug!("SMB {:?}", smb); + if smb.version == 0xff_u8 { // SMB1 + SCLogDebug!("SMBv1 record"); + if let Ok((_, ref smb_record)) = parse_smb_record(data) { + if smb_record.flags & 0x80 != 0 { + SCLogDebug!("RESPONSE {:02x}", smb_record.flags); + if direction == Direction::ToServer { + unsafe { *rdir = Direction::ToClient as u8; } + } + } else { + SCLogDebug!("REQUEST {:02x}", smb_record.flags); + if direction == Direction::ToClient { + unsafe { *rdir = Direction::ToServer as u8; } + } + } + return 1; + } + } else if smb.version == 0xfe_u8 { // SMB2 + SCLogDebug!("SMB2 record"); + if let Ok((_, ref smb_record)) = parse_smb2_record_direction(data) { + if direction == Direction::ToServer { + SCLogDebug!("direction Direction::ToServer smb_record {:?}", smb_record); + if !smb_record.request { + unsafe { *rdir = Direction::ToClient as u8; } + } + } else { + SCLogDebug!("direction Direction::ToClient smb_record {:?}", smb_record); + if smb_record.request { + unsafe { *rdir = Direction::ToServer as u8; } + } + } + } + } + else if smb.version == 0xfd_u8 { // SMB3 transform + SCLogDebug!("SMB3 record"); + } + return 1; + }, + _ => { + SCLogDebug!("smb not found in {:?}", slice); + }, + } + }, + _ => { + SCLogDebug!("no dice"); + }, + } + return 0; +} + +fn smb_probe_tcp(flags: u8, slice: &[u8], rdir: *mut u8, begins: bool) -> AppProto +{ + if flags & STREAM_MIDSTREAM == STREAM_MIDSTREAM && smb_probe_tcp_midstream(flags.into(), slice, rdir, begins) == 1 { + unsafe { return ALPROTO_SMB; } + } + if let Ok((_, ref hdr)) = parse_nbss_record_partial(slice) { + if hdr.is_smb() { + SCLogDebug!("smb found"); + unsafe { return ALPROTO_SMB; } + } else if hdr.needs_more(){ + return 0; + } else if hdr.is_valid() && + hdr.message_type != NBSS_MSGTYPE_SESSION_MESSAGE { + //we accept a first small netbios message before real SMB + let hl = hdr.length as usize; + if hdr.data.len() >= hl + 8 { + // 8 is 4 bytes NBSS + 4 bytes SMB0xFX magic + if let Ok((_, ref hdr2)) = parse_nbss_record_partial(&hdr.data[hl..]) { + if hdr2.is_smb() { + SCLogDebug!("smb found"); + unsafe { return ALPROTO_SMB; } + } + } + } else if hdr.length < 256 { + // we want more data, 256 is some random value + return 0; + } + // default is failure + } + } + SCLogDebug!("no smb"); + unsafe { return ALPROTO_FAILED; } +} + +// probing confirmation parser +// return 1 if found, 0 is not found +#[no_mangle] +pub unsafe extern "C" fn rs_smb_probe_begins_tcp(_f: *const Flow, + flags: u8, input: *const u8, len: u32, rdir: *mut u8) + -> AppProto +{ + if len < MIN_REC_SIZE as u32 { + return ALPROTO_UNKNOWN; + } + let slice = build_slice!(input, len as usize); + return smb_probe_tcp(flags, slice, rdir, true); +} + +// probing parser +// return 1 if found, 0 is not found +#[no_mangle] +pub unsafe extern "C" fn rs_smb_probe_tcp(_f: *const Flow, + flags: u8, input: *const u8, len: u32, rdir: *mut u8) + -> AppProto +{ + if len < MIN_REC_SIZE as u32 { + return ALPROTO_UNKNOWN; + } + let slice = build_slice!(input, len as usize); + return smb_probe_tcp(flags, slice, rdir, false); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_tx_count(state: *mut ffi::c_void) + -> u64 +{ + let state = cast_pointer!(state, SMBState); + SCLogDebug!("rs_smb_state_get_tx_count: returning {}", state.tx_id); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_tx(state: *mut ffi::c_void, + tx_id: u64) + -> *mut ffi::c_void +{ + let state = cast_pointer!(state, SMBState); + match state.get_tx_by_id(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_tx_free(state: *mut ffi::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state, SMBState); + SCLogDebug!("freeing tx {}", tx_id); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_tx_get_alstate_progress(tx: *mut ffi::c_void, + direction: u8) + -> i32 +{ + let tx = cast_pointer!(tx, SMBTransaction); + + if direction == Direction::ToServer as u8 && tx.request_done { + SCLogDebug!("tx {} TOSERVER progress 1 => {:?}", tx.id, tx); + return 1; + } else if direction == Direction::ToClient as u8 && tx.response_done { + SCLogDebug!("tx {} TOCLIENT progress 1 => {:?}", tx.id, tx); + return 1; + } else { + SCLogDebug!("tx {} direction {} progress 0 => {:?}", tx.id, direction, tx); + return 0; + } +} + + +export_state_data_get!(rs_smb_get_state_data, SMBState); + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, SMBTransaction); + return &mut tx.tx_data; +} + + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_truncate( + state: *mut std::ffi::c_void, + direction: u8) +{ + let state = cast_pointer!(state, SMBState); + match direction.into() { + Direction::ToServer => { + state.trunc_ts(); + } + Direction::ToClient => { + state.trunc_tc(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_event_info_by_id( + event_id: std::os::raw::c_int, + event_name: *mut *const std::os::raw::c_char, + event_type: *mut AppLayerEventType, +) -> i8 { + SMBEvent::get_event_info_by_id(event_id, event_name, event_type) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_state_get_event_info( + event_name: *const std::os::raw::c_char, + event_id: *mut std::os::raw::c_int, + event_type: *mut AppLayerEventType, +) -> std::os::raw::c_int { + SMBEvent::get_event_info(event_name, event_id, event_type) +} + +pub unsafe extern "C" fn smb3_probe_tcp(f: *const Flow, dir: u8, input: *const u8, len: u32, rdir: *mut u8) -> u16 { + let retval = rs_smb_probe_tcp(f, dir, input, len, rdir); + let f = cast_pointer!(f, Flow); + if retval != ALPROTO_SMB { + return retval; + } + let (sp, dp) = f.get_ports(); + let flags = f.get_flags(); + let fsp = if (flags & FLOW_DIR_REVERSED) != 0 { dp } else { sp }; + let fdp = if (flags & FLOW_DIR_REVERSED) != 0 { sp } else { dp }; + if fsp == 445 && fdp != 445 { + match dir.into() { + Direction::ToServer => { + *rdir = Direction::ToClient as u8; + } + Direction::ToClient => { + *rdir = Direction::ToServer as u8; + } + } + } + return ALPROTO_SMB; +} + +fn register_pattern_probe() -> i8 { + let mut r = 0; + unsafe { + // SMB1 + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|ff|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|ff|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + // SMB2/3 + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fe|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fe|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, rs_smb_probe_begins_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + // SMB3 encrypted records + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fd|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToServer as u8, smb3_probe_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + r |= AppLayerProtoDetectPMRegisterPatternCSwPP(IPPROTO_TCP, ALPROTO_SMB, + b"|fd|SMB\0".as_ptr() as *const std::os::raw::c_char, 8, 4, + Direction::ToClient as u8, smb3_probe_tcp, MIN_REC_SIZE, MIN_REC_SIZE); + } + + if r == 0 { + return 0; + } else { + return -1; + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"smb\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_smb_register_parser() { + let default_port = CString::new("445").unwrap(); + let mut stream_depth = SMB_CONFIG_DEFAULT_STREAM_DEPTH; + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: rs_smb_state_new, + state_free: rs_smb_state_free, + tx_free: rs_smb_state_tx_free, + parse_ts: rs_smb_parse_request_tcp, + parse_tc: rs_smb_parse_response_tcp, + get_tx_count: rs_smb_state_get_tx_count, + get_tx: rs_smb_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_smb_tx_get_alstate_progress, + get_eventinfo: Some(rs_smb_state_get_event_info), + get_eventinfo_byid : Some(rs_smb_state_get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: Some(rs_smb_gettxfiles), + get_tx_iterator: Some(applayer::state_get_tx_iterator::<SMBState, SMBTransaction>), + get_tx_data: rs_smb_get_tx_data, + get_state_data: rs_smb_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: Some(rs_smb_state_truncate), + get_frame_id_by_name: Some(SMBFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(SMBFrameType::ffi_name_from_id), + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_SMB = alproto; + if register_pattern_probe() < 0 { + return; + } + + let have_cfg = AppLayerProtoDetectPPParseConfPorts(ip_proto_str.as_ptr(), + IPPROTO_TCP, parser.name, ALPROTO_SMB, 0, + MIN_REC_SIZE, rs_smb_probe_tcp, rs_smb_probe_tcp); + + if have_cfg == 0 { + AppLayerProtoDetectPPRegister(IPPROTO_TCP, default_port.as_ptr(), ALPROTO_SMB, + 0, MIN_REC_SIZE, Direction::ToServer as u8, rs_smb_probe_tcp, rs_smb_probe_tcp); + } + + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust SMB parser registered."); + let retval = conf_get("app-layer.protocols.smb.stream-depth"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { stream_depth = retval as u32; } + Err(_) => { SCLogError!("Invalid depth value"); } + } + } + AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_SMB, stream_depth); + let retval = conf_get("app-layer.protocols.smb.max-read-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-queue-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_QUEUE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-queue-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-write-queue-cnt"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_WRITE_QUEUE_CNT = retval as u32; } + Err(_) => { SCLogError!("Invalid max-write-queue-cnt value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-read-queue-size"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_QUEUE_SIZE = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-queue-size value"); } + } + } + let retval = conf_get("app-layer.protocols.smb.max-read-queue-cnt"); + if let Some(val) = retval { + match get_memval(val) { + Ok(retval) => { SMB_CFG_MAX_READ_QUEUE_CNT = retval as u32; } + Err(_) => { SCLogError!("Invalid max-read-queue-cnt value"); } + } + } + if let Some(val) = conf_get("app-layer.protocols.smb.max-tx") { + if let Ok(v) = val.parse::<usize>() { + SMB_MAX_TX = v; + } else { + SCLogError!("Invalid value for smb.max-tx"); + } + } + SCLogConfig!("read: max record size: {}, max queued chunks {}, max queued size {}", + SMB_CFG_MAX_READ_SIZE, SMB_CFG_MAX_READ_QUEUE_CNT, SMB_CFG_MAX_READ_QUEUE_SIZE); + SCLogConfig!("write: max record size: {}, max queued chunks {}, max queued size {}", + SMB_CFG_MAX_WRITE_SIZE, SMB_CFG_MAX_WRITE_QUEUE_CNT, SMB_CFG_MAX_WRITE_QUEUE_SIZE); + } else { + SCLogDebug!("Protocol detector and parser disabled for SMB."); + } +} diff --git a/rust/src/smb/smb1.rs b/rust/src/smb/smb1.rs new file mode 100644 index 0000000..9d7d47e --- /dev/null +++ b/rust/src/smb/smb1.rs @@ -0,0 +1,1155 @@ +/* Copyright (C) 2018-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/* TODO + * - check all parsers for calls on non-SUCCESS status + */ + +use crate::core::*; + +use crate::smb::smb::*; +use crate::smb::dcerpc::*; +use crate::smb::events::*; +use crate::smb::files::*; + +use crate::smb::smb1_records::*; +use crate::smb::smb1_session::*; + +use crate::smb::smb_status::*; + +use nom7::Err; + +// https://msdn.microsoft.com/en-us/library/ee441741.aspx +pub const SMB1_COMMAND_CREATE_DIRECTORY: u8 = 0x00; +pub const SMB1_COMMAND_DELETE_DIRECTORY: u8 = 0x01; +pub const SMB1_COMMAND_OPEN: u8 = 0x02; +pub const SMB1_COMMAND_CREATE: u8 = 0x03; +pub const SMB1_COMMAND_CLOSE: u8 = 0x04; +pub const SMB1_COMMAND_FLUSH: u8 = 0x05; +pub const SMB1_COMMAND_DELETE: u8 = 0x06; +pub const SMB1_COMMAND_RENAME: u8 = 0x07; +pub const SMB1_COMMAND_QUERY_INFORMATION: u8 = 0x08; +pub const SMB1_COMMAND_SET_INFORMATION: u8 = 0x09; +pub const SMB1_COMMAND_READ: u8 = 0x0a; +pub const SMB1_COMMAND_WRITE: u8 = 0x0b; +pub const SMB1_COMMAND_LOCK_BYTE_RANGE: u8 = 0x0c; +pub const SMB1_COMMAND_UNLOCK_BYTE_RANGE: u8 = 0x0d; +pub const SMB1_COMMAND_CREATE_TEMPORARY: u8 = 0x0e; +pub const SMB1_COMMAND_CREATE_NEW: u8 = 0x0f; +pub const SMB1_COMMAND_CHECK_DIRECTORY: u8 = 0x10; +pub const SMB1_COMMAND_PROCESS_EXIT: u8 = 0x11; +pub const SMB1_COMMAND_SEEK: u8 = 0x12; +pub const SMB1_COMMAND_LOCK_AND_READ: u8 = 0x13; +pub const SMB1_COMMAND_WRITE_AND_UNLOCK: u8 = 0x14; +pub const SMB1_COMMAND_LOCKING_ANDX: u8 = 0x24; +pub const SMB1_COMMAND_TRANS: u8 = 0x25; +pub const SMB1_COMMAND_ECHO: u8 = 0x2b; +pub const SMB1_COMMAND_WRITE_AND_CLOSE: u8 = 0x2c; +pub const SMB1_COMMAND_OPEN_ANDX: u8 = 0x2d; +pub const SMB1_COMMAND_READ_ANDX: u8 = 0x2e; +pub const SMB1_COMMAND_WRITE_ANDX: u8 = 0x2f; +pub const SMB1_COMMAND_TRANS2: u8 = 0x32; +pub const SMB1_COMMAND_TRANS2_SECONDARY: u8 = 0x33; +pub const SMB1_COMMAND_FIND_CLOSE2: u8 = 0x34; +pub const SMB1_COMMAND_TREE_DISCONNECT: u8 = 0x71; +pub const SMB1_COMMAND_NEGOTIATE_PROTOCOL: u8 = 0x72; +pub const SMB1_COMMAND_SESSION_SETUP_ANDX: u8 = 0x73; +pub const SMB1_COMMAND_LOGOFF_ANDX: u8 = 0x74; +pub const SMB1_COMMAND_TREE_CONNECT_ANDX: u8 = 0x75; +pub const SMB1_COMMAND_QUERY_INFO_DISK: u8 = 0x80; +pub const SMB1_COMMAND_NT_TRANS: u8 = 0xa0; +pub const SMB1_COMMAND_NT_TRANS_SECONDARY: u8 = 0xa1; +pub const SMB1_COMMAND_NT_CREATE_ANDX: u8 = 0xa2; +pub const SMB1_COMMAND_NT_CANCEL: u8 = 0xa4; +pub const SMB1_COMMAND_NONE: u8 = 0xff; + +pub fn smb1_command_string(c: u8) -> String { + match c { + SMB1_COMMAND_CREATE_DIRECTORY => "SMB1_COMMAND_CREATE_DIRECTORY", + SMB1_COMMAND_DELETE_DIRECTORY => "SMB1_COMMAND_DELETE_DIRECTORY", + SMB1_COMMAND_OPEN => "SMB1_COMMAND_OPEN", + SMB1_COMMAND_CREATE => "SMB1_COMMAND_CREATE", + SMB1_COMMAND_CLOSE => "SMB1_COMMAND_CLOSE", + SMB1_COMMAND_FLUSH => "SMB1_COMMAND_FLUSH", + SMB1_COMMAND_DELETE => "SMB1_COMMAND_DELETE", + SMB1_COMMAND_RENAME => "SMB1_COMMAND_RENAME", + SMB1_COMMAND_QUERY_INFORMATION => "SMB1_COMMAND_QUERY_INFORMATION", + SMB1_COMMAND_SET_INFORMATION => "SMB1_COMMAND_SET_INFORMATION", + SMB1_COMMAND_READ => "SMB1_COMMAND_READ", + SMB1_COMMAND_WRITE => "SMB1_COMMAND_WRITE", + SMB1_COMMAND_LOCK_BYTE_RANGE => "SMB1_COMMAND_LOCK_BYTE_RANGE", + SMB1_COMMAND_UNLOCK_BYTE_RANGE => "SMB1_COMMAND_UNLOCK_BYTE_RANGE", + SMB1_COMMAND_CREATE_TEMPORARY => "SMB1_COMMAND_CREATE_TEMPORARY", + SMB1_COMMAND_CREATE_NEW => "SMB1_COMMAND_CREATE_NEW", + SMB1_COMMAND_CHECK_DIRECTORY => "SMB1_COMMAND_CHECK_DIRECTORY", + SMB1_COMMAND_PROCESS_EXIT => "SMB1_COMMAND_PROCESS_EXIT", + SMB1_COMMAND_SEEK => "SMB1_COMMAND_SEEK", + SMB1_COMMAND_LOCK_AND_READ => "SMB1_COMMAND_LOCK_AND_READ", + SMB1_COMMAND_WRITE_AND_UNLOCK => "SMB1_COMMAND_WRITE_AND_UNLOCK", + SMB1_COMMAND_LOCKING_ANDX => "SMB1_COMMAND_LOCKING_ANDX", + SMB1_COMMAND_ECHO => "SMB1_COMMAND_ECHO", + SMB1_COMMAND_WRITE_AND_CLOSE => "SMB1_COMMAND_WRITE_AND_CLOSE", + SMB1_COMMAND_OPEN_ANDX => "SMB1_COMMAND_OPEN_ANDX", + SMB1_COMMAND_READ_ANDX => "SMB1_COMMAND_READ_ANDX", + SMB1_COMMAND_WRITE_ANDX => "SMB1_COMMAND_WRITE_ANDX", + SMB1_COMMAND_TRANS => "SMB1_COMMAND_TRANS", + SMB1_COMMAND_TRANS2 => "SMB1_COMMAND_TRANS2", + SMB1_COMMAND_TRANS2_SECONDARY => "SMB1_COMMAND_TRANS2_SECONDARY", + SMB1_COMMAND_FIND_CLOSE2 => "SMB1_COMMAND_FIND_CLOSE2", + SMB1_COMMAND_TREE_DISCONNECT => "SMB1_COMMAND_TREE_DISCONNECT", + SMB1_COMMAND_NEGOTIATE_PROTOCOL => "SMB1_COMMAND_NEGOTIATE_PROTOCOL", + SMB1_COMMAND_SESSION_SETUP_ANDX => "SMB1_COMMAND_SESSION_SETUP_ANDX", + SMB1_COMMAND_LOGOFF_ANDX => "SMB1_COMMAND_LOGOFF_ANDX", + SMB1_COMMAND_TREE_CONNECT_ANDX => "SMB1_COMMAND_TREE_CONNECT_ANDX", + SMB1_COMMAND_QUERY_INFO_DISK => "SMB1_COMMAND_QUERY_INFO_DISK", + SMB1_COMMAND_NT_TRANS => "SMB1_COMMAND_NT_TRANS", + SMB1_COMMAND_NT_TRANS_SECONDARY => "SMB1_COMMAND_NT_TRANS_SECONDARY", + SMB1_COMMAND_NT_CREATE_ANDX => "SMB1_COMMAND_NT_CREATE_ANDX", + SMB1_COMMAND_NT_CANCEL => "SMB1_COMMAND_NT_CANCEL", + _ => { return (c).to_string(); }, + }.to_string() +} + +// later we'll use this to determine if we need to +// track a ssn per type +pub fn smb1_create_new_tx(cmd: u8) -> bool { + match cmd { + SMB1_COMMAND_READ_ANDX | + SMB1_COMMAND_WRITE_ANDX | + SMB1_COMMAND_TRANS | + SMB1_COMMAND_TRANS2 => { false }, + _ => { true }, + } +} + +// see if we're going to do a lookup for a TX. +// related to smb1_create_new_tx(), however it +// excludes the 'maybe' commands like TRANS2 +pub fn smb1_check_tx(cmd: u8) -> bool { + match cmd { + SMB1_COMMAND_READ_ANDX | + SMB1_COMMAND_WRITE_ANDX | + SMB1_COMMAND_TRANS => { false }, + _ => { true }, + } +} + +fn smb1_close_file(state: &mut SMBState, fid: &[u8], direction: Direction) +{ + if let Some(tx) = state.get_file_tx_by_fuid(fid, direction) { + SCLogDebug!("found tx {}", tx.id); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if !tx.request_done { + SCLogDebug!("closing file tx {} FID {:?}", tx.id, fid); + filetracker_close(&mut tdf.file_tracker); + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + } + } + } +} + +fn smb1_command_is_andx(c: u8) -> bool { + match c { + SMB1_COMMAND_LOCKING_ANDX | + SMB1_COMMAND_OPEN_ANDX | + SMB1_COMMAND_READ_ANDX | + SMB1_COMMAND_SESSION_SETUP_ANDX | + SMB1_COMMAND_LOGOFF_ANDX | + SMB1_COMMAND_TREE_CONNECT_ANDX | + SMB1_COMMAND_NT_CREATE_ANDX | + SMB1_COMMAND_WRITE_ANDX => { + return true; + } + _ => { + return false; + } + } +} + +fn smb1_request_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, andx_offset: &mut usize) { + let mut events : Vec<SMBEvent> = Vec::new(); + let mut no_response_expected = false; + + let have_tx = match command { + SMB1_COMMAND_RENAME => { + match parse_smb_rename_request_record(r.data) { + Ok((_, rd)) => { + SCLogDebug!("RENAME {:?}", rd); + + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let mut newname = rd.newname; + newname.retain(|&i|i != 0x00); + let mut oldname = rd.oldname; + oldname.retain(|&i|i != 0x00); + + let tx = state.new_rename_tx(Vec::new(), oldname, newname); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_RENAME); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TRANS2 => { + match parse_smb_trans2_request_record(r.data) { + Ok((_, rd)) => { + SCLogDebug!("TRANS2 DONE {:?}", rd); + + if rd.subcmd == 6 { + SCLogDebug!("SET_PATH_INFO"); + match parse_trans2_request_params_set_path_info(rd.setup_blob) { + Ok((_, pd)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO PARAMS DONE {:?}", pd); + + if pd.loi == 1013 { // set disposition info + match parse_trans2_request_data_set_file_info_disposition(rd.data_blob) { + Ok((_, disp)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION DONE {:?}", disp); + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + + let tx = state.new_setpathinfo_tx(pd.oldname, + rd.subcmd, pd.loi, disp.delete); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TRANS2); + true + + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else if pd.loi == 1010 { + match parse_trans2_request_data_set_path_info_rename(rd.data_blob) { + Ok((_, ren)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO DATA RENAME DONE {:?}", ren); + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let mut newname = ren.newname.to_vec(); + newname.retain(|&i|i != 0x00); + + let fid : Vec<u8> = Vec::new(); + + let tx = state.new_rename_tx(fid, pd.oldname, newname); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TRANS2); + true + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO DATA RENAME INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO DATA RENAME ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + false + } + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO PARAMS INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_PATH_INFO PARAMS ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else if rd.subcmd == 8 { + SCLogDebug!("SET_FILE_INFO"); + match parse_trans2_request_params_set_file_info(rd.setup_blob) { + Ok((_, pd)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO PARAMS DONE {:?}", pd); + + if pd.loi == 1013 { // set disposition info + match parse_trans2_request_data_set_file_info_disposition(rd.data_blob) { + Ok((_, disp)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION DONE {:?}", disp); + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + + let mut frankenfid = pd.fid.to_vec(); + frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + + let filename = match state.guid2name_map.get(&frankenfid) { + Some(n) => n.to_vec(), + None => b"<unknown>".to_vec(), + }; + let tx = state.new_setfileinfo_tx(filename, pd.fid.to_vec(), + rd.subcmd, pd.loi, disp.delete); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TRANS2); + true + + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA DISPOSITION ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else if pd.loi == 1010 { + match parse_trans2_request_data_set_file_info_rename(rd.data_blob) { + Ok((_, ren)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA RENAME DONE {:?}", ren); + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let mut newname = ren.newname.to_vec(); + newname.retain(|&i|i != 0x00); + + let mut frankenfid = pd.fid.to_vec(); + frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + + let oldname = match state.guid2name_map.get(&frankenfid) { + Some(n) => n.to_vec(), + None => b"<unknown>".to_vec(), + }; + let tx = state.new_rename_tx(pd.fid.to_vec(), oldname, newname); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TRANS2); + true + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA RENAME INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO DATA RENAME ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + false + } + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO PARAMS INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 SET_FILE_INFO PARAMS ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + false + } + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("TRANS2 INCOMPLETE {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("TRANS2 ERROR {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_READ_ANDX => { + match parse_smb_read_andx_request_record(&r.data[*andx_offset-SMB1_HEADER_SIZE..]) { + Ok((_, rr)) => { + SCLogDebug!("rr {:?}", rr); + + // store read fid,offset in map + let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET); + let mut fid = rr.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + let fidoff = SMBFileGUIDOffset::new(fid, rr.offset); + state.ssn2vecoffset_map.insert(fid_key, fidoff); + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB1_COMMAND_WRITE_ANDX | + SMB1_COMMAND_WRITE | + SMB1_COMMAND_WRITE_AND_CLOSE => { + smb1_write_request_record(state, r, *andx_offset, command, 0); + true // tx handling in func + }, + SMB1_COMMAND_TRANS => { + smb1_trans_request_record(state, r); + true + }, + SMB1_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb1_negotiate_protocol_record(r.data) { + Ok((_, pr)) => { + SCLogDebug!("SMB_COMMAND_NEGOTIATE_PROTOCOL {:?}", pr); + + let mut bad_dialects = false; + let mut dialects : Vec<Vec<u8>> = Vec::new(); + for d in &pr.dialects { + if d.is_empty() { + bad_dialects = true; + continue; + } else if d.len() == 1 { + bad_dialects = true; + } + let x = &d[1..d.len()]; + let dvec = x.to_vec(); + dialects.push(dvec); + } + + let found = match state.get_negotiate_tx(1) { + Some(tx) => { + SCLogDebug!("WEIRD, should not have NEGOTIATE tx!"); + tx.set_event(SMBEvent::DuplicateNegotiate); + true + }, + None => { false }, + }; + if !found { + let tx = state.new_negotiate_tx(1); + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.dialects = dialects; + } + tx.request_done = true; + if bad_dialects { + tx.set_event(SMBEvent::NegotiateMalformedDialects); + } + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_NT_CREATE_ANDX => { + match parse_smb_create_andx_request_record(&r.data[*andx_offset-SMB1_HEADER_SIZE..], r) { + Ok((_, cr)) => { + SCLogDebug!("Create AndX {:?}", cr); + let del = cr.create_options & 0x0000_1000 != 0; + let dir = cr.create_options & 0x0000_0001 != 0; + SCLogDebug!("del {} dir {} options {:08x}", del, dir, cr.create_options); + + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME); + let name_val = cr.file_name.to_vec(); + state.ssn2vec_map.insert(name_key, name_val); + + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_create_tx(&cr.file_name.to_vec(), + cr.disposition, del, dir, tx_hdr); + tx.vercmd.set_smb1_cmd(command); + SCLogDebug!("TS CREATE TX {} created", tx.id); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_SESSION_SETUP_ANDX => { + SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); + smb1_session_setup_request(state, r, *andx_offset); + true + }, + SMB1_COMMAND_TREE_CONNECT_ANDX => { + SCLogDebug!("SMB1_COMMAND_TREE_CONNECT_ANDX"); + match parse_smb_connect_tree_andx_record(&r.data[*andx_offset-SMB1_HEADER_SIZE..], r) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + let mut name_val = tr.path; + if name_val.len() > 1 { + name_val = name_val[1..].to_vec(); + } + + // store hdr as SMBHDR_TYPE_TREE, so with tree id 0 + // when the response finds this we update it + let tx = state.new_treeconnect_tx(name_key, name_val); + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.req_service = Some(tr.service.to_vec()); + } + tx.request_done = true; + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_TREE_CONNECT_ANDX); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_DISCONNECT => { + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + }, + SMB1_COMMAND_CLOSE => { + match parse_smb1_close_request_record(r.data) { + Ok((_, cd)) => { + let mut fid = cd.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + state.ssn2vec_map.insert(SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID), fid.to_vec()); + + SCLogDebug!("closing FID {:?}/{:?}", cd.fid, fid); + smb1_close_file(state, &fid, Direction::ToServer); + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB1_COMMAND_NT_CANCEL | + SMB1_COMMAND_TRANS2_SECONDARY | + SMB1_COMMAND_LOCKING_ANDX => { + no_response_expected = true; + false + }, + _ => { + if command == SMB1_COMMAND_LOGOFF_ANDX || + command == SMB1_COMMAND_TREE_DISCONNECT || + command == SMB1_COMMAND_NT_TRANS || + command == SMB1_COMMAND_NT_TRANS_SECONDARY || + command == SMB1_COMMAND_NT_CANCEL || + command == SMB1_COMMAND_RENAME || + command == SMB1_COMMAND_CHECK_DIRECTORY || + command == SMB1_COMMAND_ECHO || + command == SMB1_COMMAND_TRANS + { } else { + SCLogDebug!("unsupported command {}/{}", + command, &smb1_command_string(command)); + } + false + }, + }; + if !have_tx && smb1_create_new_tx(command) { + let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_generic_tx(1, command as u16, tx_key); + SCLogDebug!("tx {} created for {}/{}", tx.id, command, &smb1_command_string(command)); + tx.set_events(events); + if no_response_expected { + tx.response_done = true; + } + } +} + +pub fn smb1_request_record(state: &mut SMBState, r: &SmbRecord) -> u32 { + SCLogDebug!("record: command {}: record {:?}", r.command, r); + + let mut andx_offset = SMB1_HEADER_SIZE; + let mut command = r.command; + loop { + smb1_request_record_one(state, r, command, &mut andx_offset); + + // continue for next andx command if any + if smb1_command_is_andx(command) { + if let Ok((_, andx_hdr)) = smb1_parse_andx_header(&r.data[andx_offset-SMB1_HEADER_SIZE..]) { + if (andx_hdr.andx_offset as usize) > andx_offset && + andx_hdr.andx_command != SMB1_COMMAND_NONE && + (andx_hdr.andx_offset as usize) - SMB1_HEADER_SIZE < r.data.len() { + andx_offset = andx_hdr.andx_offset as usize; + command = andx_hdr.andx_command; + continue; + } + } + } + break; + } + + 0 +} + +fn smb1_response_record_one(state: &mut SMBState, r: &SmbRecord, command: u8, andx_offset: &mut usize) { + SCLogDebug!("record: command {} status {} -> {:?}", r.command, r.nt_status, r); + + let key_ssn_id = r.ssn_id; + let key_tree_id = r.tree_id; + let key_multiplex_id = r.multiplex_id; + let mut tx_sync = false; + let mut events : Vec<SMBEvent> = Vec::new(); + + let have_tx = match command { + SMB1_COMMAND_READ_ANDX => { + smb1_read_response_record(state, r, *andx_offset, 0); + true // tx handling in func + }, + SMB1_COMMAND_NEGOTIATE_PROTOCOL => { + SCLogDebug!("SMB1_COMMAND_NEGOTIATE_PROTOCOL response"); + match parse_smb1_negotiate_protocol_response_record(r.data) { + Ok((_, pr)) => { + let (have_ntx, dialect) = match state.get_negotiate_tx(1) { + Some(tx) => { + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + let d = match tx.type_data { + Some(SMBTransactionTypeData::NEGOTIATE(ref mut x)) => { + x.server_guid = pr.server_guid.to_vec(); + + let dialect_idx = pr.dialect_idx as usize; + if x.dialects.len() <= dialect_idx { + None + } else { + let d = x.dialects[dialect_idx].to_vec(); + Some(d) + } + }, + _ => { None }, + }; + if d.is_none() { + tx.set_event(SMBEvent::NegotiateMalformedDialects); + } + (true, d) + }, + None => { (false, None) }, + }; + if let Some(d) = dialect { + SCLogDebug!("dialect {:?}", d); + state.dialect_vec = Some(d); + } + have_ntx + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_CONNECT_ANDX => { + if r.nt_status != SMB_NTSTATUS_SUCCESS { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + if let Some(tx) = state.get_treeconnect_tx(name_key) { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.tree_id = r.tree_id as u32; + } + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + } + return; + } + + match parse_smb_connect_tree_andx_response_record(&r.data[*andx_offset-SMB1_HEADER_SIZE..]) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_TREE); + let is_pipe = tr.service == "IPC".as_bytes(); + let mut share_name = Vec::new(); + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.is_pipe = is_pipe; + tdn.tree_id = r.tree_id as u32; + share_name = tdn.share_name.to_vec(); + tdn.res_service = Some(tr.service.to_vec()); + } + tx.hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; + SCLogDebug!("tx {} is done", tx.id); + true + }, + None => { false }, + }; + if found { + let tree = SMBTree::new(share_name.to_vec(), is_pipe); + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.insert(tree_key, tree); + } + found + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB1_COMMAND_TREE_DISCONNECT => { + // normally removed when processing request, + // but in case we missed that try again here + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + }, + SMB1_COMMAND_NT_CREATE_ANDX => { + SCLogDebug!("SMB1_COMMAND_NT_CREATE_ANDX response {:08x}", r.nt_status); + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb_create_andx_response_record(&r.data[*andx_offset-SMB1_HEADER_SIZE..]) { + Ok((_, cr)) => { + SCLogDebug!("Create AndX {:?}", cr); + + let guid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_FILENAME); + match state.ssn2vec_map.remove(&guid_key) { + Some(mut p) => { + p.retain(|&i|i != 0x00); + + let mut fid = cr.fid.to_vec(); + fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + SCLogDebug!("SMB1_COMMAND_NT_CREATE_ANDX fid {:?}", fid); + SCLogDebug!("fid {:?} name {:?}", fid, p); + state.guid2name_map.insert(fid, p); + }, + _ => { + SCLogDebug!("SMBv1 response: GUID NOT FOUND"); + }, + } + + let tx_hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + if let Some(tx) = state.get_generic_tx(1, command as u16, &tx_hdr) { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, command, &smb1_command_string(command)); + tx.set_status(r.nt_status, false); + tx.response_done = true; + + if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data { + tdn.create_ts = cr.create_ts.as_unix(); + tdn.last_access_ts = cr.last_access_ts.as_unix(); + tdn.last_write_ts = cr.last_write_ts.as_unix(); + tdn.last_change_ts = cr.last_change_ts.as_unix(); + tdn.size = cr.file_size; + tdn.guid = cr.fid.to_vec(); + } + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + false + } + }, + SMB1_COMMAND_CLOSE => { + let fid = state.ssn2vec_map.remove(&SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID)); + if let Some(fid) = fid { + SCLogDebug!("closing FID {:?}", fid); + smb1_close_file(state, &fid, Direction::ToClient); + } + false + }, + SMB1_COMMAND_TRANS => { + smb1_trans_response_record(state, r); + true + }, + SMB1_COMMAND_SESSION_SETUP_ANDX => { + smb1_session_setup_response(state, r, *andx_offset); + true + }, + SMB1_COMMAND_LOGOFF_ANDX => { + tx_sync = true; + false + }, + _ => { + false + }, + }; + + if !have_tx && tx_sync { + if let Some(tx) = state.get_last_tx(1, command as u16) { + SCLogDebug!("last TX {} is CMD {}", tx.id, &smb1_command_string(command)); + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + } + } else if !have_tx && smb1_check_tx(command) { + let tx_key = SMBCommonHdr::new(SMBHDR_TYPE_GENERICTX, + key_ssn_id as u64, key_tree_id as u32, key_multiplex_id as u64); + let _have_tx2 = match state.get_generic_tx(1, command as u16, &tx_key) { + Some(tx) => { + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + true + }, + None => { + SCLogDebug!("no TX found for key {:?}", tx_key); + false + }, + }; + } else { + SCLogDebug!("have tx for cmd {}", command); + } +} + +pub fn smb1_response_record(state: &mut SMBState, r: &SmbRecord) -> u32 { + let mut andx_offset = SMB1_HEADER_SIZE; + let mut command = r.command; + loop { + smb1_response_record_one(state, r, command, &mut andx_offset); + + // continue for next andx command if any + if smb1_command_is_andx(command) { + if let Ok((_, andx_hdr)) = smb1_parse_andx_header(&r.data[andx_offset-SMB1_HEADER_SIZE..]) { + if (andx_hdr.andx_offset as usize) > andx_offset && + andx_hdr.andx_command != SMB1_COMMAND_NONE && + (andx_hdr.andx_offset as usize) - SMB1_HEADER_SIZE < r.data.len() { + andx_offset = andx_hdr.andx_offset as usize; + command = andx_hdr.andx_command; + continue; + } + } + } + break; + } + + 0 +} + +pub fn smb1_trans_request_record(state: &mut SMBState, r: &SmbRecord) +{ + let mut events : Vec<SMBEvent> = Vec::new(); + + match parse_smb_trans_request_record(r.data, r) { + Ok((_, rd)) => { + SCLogDebug!("TRANS request {:?}", rd); + + /* if we have a fid, store it so the response can pick it up */ + let mut pipe_dcerpc = false; + if rd.pipe.is_some() { + let pipe = rd.pipe.unwrap(); + state.ssn2vec_map.insert(SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID), + pipe.fid.to_vec()); + + let mut frankenfid = pipe.fid.to_vec(); + frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + + let (_filename, is_dcerpc) = state.get_service_for_guid(&frankenfid); + + SCLogDebug!("smb1_trans_request_record: name {} is_dcerpc {}", + _filename, is_dcerpc); + pipe_dcerpc = is_dcerpc; + } + + if pipe_dcerpc { + SCLogDebug!("SMBv1 TRANS TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1(r.command); + smb_write_dcerpc_record(state, vercmd, hdr, rd.data.data); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + smb1_request_record_generic(state, r, events); +} + +pub fn smb1_trans_response_record(state: &mut SMBState, r: &SmbRecord) +{ + let mut events : Vec<SMBEvent> = Vec::new(); + + match parse_smb_trans_response_record(r.data) { + Ok((_, rd)) => { + SCLogDebug!("TRANS response {:?}", rd); + + // see if we have a stored fid + let fid = match state.ssn2vec_map.remove( + &SMBCommonHdr::from1(r, SMBHDR_TYPE_GUID)) { + Some(f) => f, + None => Vec::new(), + }; + SCLogDebug!("FID {:?}", fid); + + let mut frankenfid = fid.to_vec(); + frankenfid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + + let (_filename, is_dcerpc) = state.get_service_for_guid(&frankenfid); + + SCLogDebug!("smb1_trans_response_record: name {} is_dcerpc {}", + _filename, is_dcerpc); + + // if we get status 'BUFFER_OVERFLOW' this is only a part of + // the data. Store it in the ssn/tree for later use. + if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + let key = SMBHashKeyHdrGuid::new(SMBCommonHdr::from1(r, SMBHDR_TYPE_TRANS_FRAG), fid); + SCLogDebug!("SMBv1/TRANS: queueing data for len {} key {:?}", rd.data.len(), key); + state.ssnguid2vec_map.insert(key, rd.data.to_vec()); + } else if is_dcerpc { + SCLogDebug!("SMBv1 TRANS TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1_with_ntstatus(r.command, r.nt_status); + smb_read_dcerpc_record(state, vercmd, hdr, &fid, rd.data); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + + // generic tx as well. Set events if needed. + smb1_response_record_generic(state, r, events); +} + +/// Handle WRITE, WRITE_ANDX, WRITE_AND_CLOSE request records +pub fn smb1_write_request_record(state: &mut SMBState, r: &SmbRecord, andx_offset: usize, command: u8, nbss_remaining: u32) +{ + let mut events : Vec<SMBEvent> = Vec::new(); + + let result = if command == SMB1_COMMAND_WRITE_ANDX { + parse_smb1_write_andx_request_record(&r.data[andx_offset-SMB1_HEADER_SIZE..], andx_offset) + } else if command == SMB1_COMMAND_WRITE { + parse_smb1_write_request_record(r.data) + } else { + parse_smb1_write_and_close_request_record(r.data) + }; + match result { + Ok((_, rd)) => { + SCLogDebug!("SMBv1: write andx => {:?}", rd); + if rd.len > rd.data.len() as u32 + nbss_remaining { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::WriteRequestTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToServer, nbss_remaining); + return; + } + let mut file_fid = rd.fid.to_vec(); + file_fid.extend_from_slice(&u32_as_bytes(r.ssn_id)); + SCLogDebug!("SMBv1 WRITE: FID {:?} offset {}", + file_fid, rd.offset); + + let file_name = match state.guid2name_map.get(&file_fid) { + Some(n) => n.to_vec(), + None => b"<unknown>".to_vec(), + }; + let mut set_event_fileoverlap = false; + let found = match state.get_file_tx_by_fuid_with_open_file(&file_fid, Direction::ToServer) { + Some(tx) => { + let file_id : u32 = tx.id as u32; + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + if rd.offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, rd.offset, + rd.len, false, &file_id); + SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx); + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + let (share_name, is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.name.to_vec(), n.is_pipe), + None => (Vec::new(), false), + }; + if is_pipe { + SCLogDebug!("SMBv1 WRITE TO PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1_with_ntstatus(command, r.nt_status); + smb_write_dcerpc_record(state, vercmd, hdr, rd.data); + } else { + let tx = state.new_file_tx(&file_fid, &file_name, Direction::ToServer); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if rd.offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, rd.offset, + rd.len, false, &file_id); + tdf.share_name = share_name; + SCLogDebug!("tdf {:?}", tdf); + } + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_WRITE_ANDX); + SCLogDebug!("FID {:?} found at tx {} => {:?}", file_fid, tx.id, tx); + } + } + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + + state.set_file_left(Direction::ToServer, rd.len, rd.data.len() as u32, file_fid.to_vec()); + + if command == SMB1_COMMAND_WRITE_AND_CLOSE { + SCLogDebug!("closing FID {:?}", file_fid); + smb1_close_file(state, &file_fid, Direction::ToServer); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + smb1_request_record_generic(state, r, events); +} + +pub fn smb1_read_response_record(state: &mut SMBState, r: &SmbRecord, andx_offset: usize, nbss_remaining: u32) +{ + let mut events : Vec<SMBEvent> = Vec::new(); + + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb_read_andx_response_record(&r.data[andx_offset-SMB1_HEADER_SIZE..]) { + Ok((_, rd)) => { + SCLogDebug!("SMBv1: read response => {:?}", rd); + if rd.len > nbss_remaining + rd.data.len() as u32 { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::ReadResponseTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + let fid_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_OFFSET); + let (offset, file_fid) = match state.ssn2vecoffset_map.remove(&fid_key) { + Some(o) => (o.offset, o.guid), + None => { + SCLogDebug!("SMBv1 READ response: reply to unknown request: left {} {:?}", + rd.len - rd.data.len() as u32, rd); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + }, + }; + SCLogDebug!("SMBv1 READ: FID {:?} offset {}", file_fid, offset); + + let tree_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_SHARE); + let (is_pipe, share_name) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.is_pipe, n.name.to_vec()), + _ => { (false, Vec::new()) }, + }; + if !is_pipe { + let file_name = match state.guid2name_map.get(&file_fid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + let mut set_event_fileoverlap = false; + let found = match state.get_file_tx_by_fuid_with_open_file(&file_fid, Direction::ToClient) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, offset, + rd.len, false, &file_id); + } + true + }, + None => { false }, + }; + if !found { + let tx = state.new_file_tx(&file_fid, &file_name, Direction::ToClient); + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + SCLogDebug!("FID {:?} found at tx {}", file_fid, tx.id); + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, offset, + rd.len, false, &file_id); + tdf.share_name = share_name; + } + tx.vercmd.set_smb1_cmd(SMB1_COMMAND_READ_ANDX); + } + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + } else { + SCLogDebug!("SMBv1 READ response from PIPE"); + let hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new1(SMB1_COMMAND_READ_ANDX); + + // hack: we store fid with ssn id mixed in, but here we want the + // real thing instead. + let pure_fid = if file_fid.len() > 2 { &file_fid[0..2] } else { &[] }; + smb_read_dcerpc_record(state, vercmd, hdr, pure_fid, rd.data); + } + + state.set_file_left(Direction::ToClient, rd.len, rd.data.len() as u32, file_fid.to_vec()); + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + } + + // generic tx as well. Set events if needed. + smb1_response_record_generic(state, r, events); +} + +/// create a tx for a command / response pair if we're +/// configured to do so, or if this is a tx especially +/// for setting an event. +fn smb1_request_record_generic(state: &mut SMBState, r: &SmbRecord, events: Vec<SMBEvent>) { + if smb1_create_new_tx(r.command) || !events.is_empty() { + let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_generic_tx(1, r.command as u16, tx_key); + tx.set_events(events); + } +} + +/// update or create a tx for a command / response pair based +/// on the response. We only create a tx for the response side +/// if we didn't already update a tx, and we have to set events +fn smb1_response_record_generic(state: &mut SMBState, r: &SmbRecord, events: Vec<SMBEvent>) { + let tx_key = SMBCommonHdr::from1(r, SMBHDR_TYPE_GENERICTX); + if let Some(tx) = state.get_generic_tx(1, r.command as u16, &tx_key) { + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, r.command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + return; + } + if !events.is_empty() { + let tx = state.new_generic_tx(1, r.command as u16, tx_key); + tx.request_done = true; + tx.response_done = true; + SCLogDebug!("tx {} cmd {} is done", tx.id, r.command); + tx.set_status(r.nt_status, r.is_dos_error); + tx.set_events(events); + } +} diff --git a/rust/src/smb/smb1_records.rs b/rust/src/smb/smb1_records.rs new file mode 100644 index 0000000..bf767da --- /dev/null +++ b/rust/src/smb/smb1_records.rs @@ -0,0 +1,880 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::common::nom7::take_until_and_consume; +use crate::smb::error::SmbError; +use crate::smb::smb::*; +use crate::smb::smb_records::*; +use nom7::bytes::streaming::{tag, take}; +use nom7::combinator::{complete, cond, peek, rest, verify}; +use nom7::error::{make_error, ErrorKind}; +use nom7::Err; +use nom7::multi::many1; +use nom7::number::streaming::{le_u8, le_u16, le_u32, le_u64}; +use nom7::IResult; + +pub const SMB1_HEADER_SIZE: usize = 32; + +// SMB_FLAGS_REPLY in Microsoft docs. +const SMB1_FLAGS_RESPONSE: u8 = 0x80; + +fn smb_get_unicode_string_with_offset(i: &[u8], offset: usize) -> IResult<&[u8], Vec<u8>, SmbError> +{ + let (i, _) = cond(offset % 2 == 1, take(1_usize))(i)?; + smb_get_unicode_string(i) +} + +/// take a string, unicode or ascii based on record +pub fn smb1_get_string<'a>(i: &'a[u8], r: &SmbRecord, offset: usize) -> IResult<&'a[u8], Vec<u8>, SmbError> { + if r.has_unicode_support() { + smb_get_unicode_string_with_offset(i, offset) + } else { + smb_get_ascii_string(i) + } +} + + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbParamBlockAndXHeader { + pub wct: u8, + pub andx_command: u8, + pub andx_offset: u16, +} + +pub fn smb1_parse_andx_header(i: &[u8]) -> IResult<&[u8], SmbParamBlockAndXHeader> { + let (i, wct) = le_u8(i)?; + let (i, andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, andx_offset) = le_u16(i)?; + let hdr = SmbParamBlockAndXHeader { + wct, + andx_command, + andx_offset, + }; + Ok((i, hdr)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Smb1WriteRequestRecord<'a> { + pub offset: u64, + pub len: u32, + pub fid: &'a[u8], + pub data: &'a[u8], +} + +pub fn parse_smb1_write_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord> { + let (i, _wct) = le_u8(i)?; + let (i, fid) = take(2_usize)(i)?; + let (i, _count) = le_u16(i)?; + let (i, offset) = le_u32(i)?; + let (i, _remaining) = le_u16(i)?; + let (i, _bcc) = le_u16(i)?; + let (i, _buffer_format) = le_u8(i)?; + let (i, data_len) = le_u16(i)?; + let (i, file_data) = take(data_len)(i)?; + let record = Smb1WriteRequestRecord { + offset: offset as u64, + len: data_len as u32, + fid, + data:file_data, + }; + Ok((i, record)) +} + +pub fn parse_smb1_write_andx_request_record(i : &[u8], andx_offset: usize) -> IResult<&[u8], Smb1WriteRequestRecord> { + let origin_i = i; + let ax = andx_offset as u16; + let (i, wct) = le_u8(i)?; + let (i, _andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _andx_offset) = le_u16(i)?; + let (i, fid) = take(2_usize)(i)?; + let (i, offset) = le_u32(i)?; + let (i, _) = take(4_usize)(i)?; // reserved + let (i, _write_mode) = le_u16(i)?; + let (i, _remaining) = le_u16(i)?; + let (i, data_len_high) = le_u16(i)?; + let (i, data_len_low) = le_u16(i)?; + let data_len = ((data_len_high as u32) << 16)|(data_len_low as u32); + let (i, data_offset) = le_u16(i)?; + if data_offset < 0x3c || data_offset < ax{ + return Err(Err::Error(make_error(i, ErrorKind::LengthValue))); + } + let (i, high_offset) = cond(wct == 14, le_u32)(i)?; + let (_i, _bcc) = le_u16(i)?; + let (i, _padding_data) = take(data_offset-ax)(origin_i)?; + let (i, file_data) = take(std::cmp::min(data_len, i.len() as u32))(i)?; + + let record = Smb1WriteRequestRecord { + offset: ((high_offset.unwrap_or(0) as u64) << 32) | offset as u64, + len: data_len, + fid, + data: file_data, + }; + Ok((i, record)) +} + +pub fn parse_smb1_write_and_close_request_record(i: &[u8]) -> IResult<&[u8], Smb1WriteRequestRecord> { + let (i, _wct) = le_u8(i)?; + let (i, fid) = take(2_usize)(i)?; + let (i, count) = le_u16(i)?; + let (i, offset) = le_u32(i)?; + let (i, _last_write) = take(4_usize)(i)?; + let (i, bcc) = le_u16(i)?; + let (i, _padding) = cond(bcc > count, |b| take(bcc - count)(b))(i)?; + let (i, file_data) = take(count)(i)?; + let record = Smb1WriteRequestRecord { + offset: offset as u64, + len: count as u32, + fid, + data: file_data, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Smb1NegotiateProtocolResponseRecord<'a> { + pub dialect_idx: u16, + pub server_guid: &'a[u8], +} + +pub fn parse_smb1_negotiate_protocol_response_record_error(i: &[u8]) + -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> { + let (i, _wct) = le_u8(i)?; + let (i, _bcc) = le_u16(i)?; + let record = Smb1NegotiateProtocolResponseRecord { + dialect_idx: 0, + server_guid: &[], + }; + Ok((i, record)) +} + +pub fn parse_smb1_negotiate_protocol_response_record_ok(i: &[u8]) + -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> { + let (i, _wct) = le_u8(i)?; + let (i, dialect_idx) = le_u16(i)?; + let (i, _sec_mode) = le_u8(i)?; + let (i, _) = take(16_usize)(i)?; + let (i, _caps) = le_u32(i)?; + let (i, _sys_time) = le_u64(i)?; + let (i, _server_tz) = le_u16(i)?; + let (i, _challenge_len) = le_u8(i)?; + let (i, bcc) = le_u16(i)?; + let (i, server_guid) = cond(bcc >= 16, take(16_usize))(i)?; + let record = Smb1NegotiateProtocolResponseRecord { + dialect_idx, + server_guid: server_guid.unwrap_or(&[]), + }; + Ok((i, record)) +} + +pub fn parse_smb1_negotiate_protocol_response_record(i: &[u8]) + -> IResult<&[u8], Smb1NegotiateProtocolResponseRecord> { + let (i, wct) = peek(le_u8)(i)?; + match wct { + 0 => parse_smb1_negotiate_protocol_response_record_error(i), + _ => parse_smb1_negotiate_protocol_response_record_ok(i), + } +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Smb1NegotiateProtocolRecord<'a> { + pub dialects: Vec<&'a [u8]>, +} + +pub fn parse_smb1_negotiate_protocol_record(i: &[u8]) + -> IResult<&[u8], Smb1NegotiateProtocolRecord> { + let (i, _wtc) = le_u8(i)?; + let (i, _bcc) = le_u16(i)?; + // dialects is a list of [1 byte buffer format][string][0 terminator] + let (i, dialects) = many1(complete(take_until_and_consume(b"\0")))(i)?; + let record = Smb1NegotiateProtocolRecord { dialects }; + Ok((i, record)) +} + + +#[derive(Debug,PartialEq, Eq)] +pub struct Smb1ResponseRecordTreeConnectAndX<'a> { + pub service: &'a[u8], + pub nativefs: &'a[u8], +} + +pub fn parse_smb_connect_tree_andx_response_record(i: &[u8]) + -> IResult<&[u8], Smb1ResponseRecordTreeConnectAndX> { + let (i, wct) = le_u8(i)?; + let (i, _andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _andx_offset) = le_u16(i)?; + let (i, _) = cond(wct >= 3, take(2_usize))(i)?; // optional support + let (i, _) = cond(wct == 7, take(8_usize))(i)?; // access masks + let (i, _bcc) = le_u16(i)?; + let (i, service) = take_until_and_consume(b"\x00")(i)?; + let (i, nativefs) = take_until_and_consume(b"\x00")(i)?; + let record = Smb1ResponseRecordTreeConnectAndX { + service, + nativefs + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordTreeConnectAndX<'a> { + pub path: Vec<u8>, + pub service: &'a[u8], +} + +pub fn parse_smb_connect_tree_andx_record<'a>(i: &'a[u8], r: &SmbRecord) + -> IResult<&'a[u8], SmbRecordTreeConnectAndX<'a>, SmbError> { + let (i, _skip1) = take(7_usize)(i)?; + let (i, pwlen) = le_u16(i)?; + let (i, _bcc) = le_u16(i)?; + let (i, _pw) = take(pwlen)(i)?; + let (i, path) = smb1_get_string(i, r, 11 + pwlen as usize)?; + let (i, service) = take_until_and_consume(b"\x00")(i)?; + let record = SmbRecordTreeConnectAndX { + path, + service + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordTransRequest<'a> { + pub params: SmbRecordTransRequestParams, + pub pipe: Option<SmbPipeProtocolRecord<'a>>, + pub txname: Vec<u8>, + pub data: SmbRecordTransRequestData<'a>, +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbPipeProtocolRecord<'a> { + pub function: u16, + pub fid: &'a[u8], +} + +pub fn parse_smb_trans_request_record_pipe(i: &[u8]) + -> IResult<&[u8], SmbPipeProtocolRecord, SmbError> { + let (i, fun) = le_u16(i)?; + let (i, fid) = take(2_usize)(i)?; + let record = SmbPipeProtocolRecord { + function: fun, + fid + }; + Ok((i, record)) +} + + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordTransRequestParams<> { + pub max_data_cnt: u16, + param_cnt: u16, + param_offset: u16, + data_cnt: u16, + data_offset: u16, + bcc: u16, +} + +pub fn parse_smb_trans_request_record_params(i: &[u8]) + -> IResult<&[u8], (SmbRecordTransRequestParams, Option<SmbPipeProtocolRecord>), SmbError> +{ + let (i, wct) = le_u8(i)?; + let (i, _total_param_cnt) = le_u16(i)?; + let (i, _total_data_count) = le_u16(i)?; + let (i, _max_param_cnt) = le_u16(i)?; + let (i, max_data_cnt) = le_u16(i)?; + let (i, _max_setup_cnt) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _) = take(2_usize)(i)?; // flags + let (i, _timeout) = le_u32(i)?; + let (i, _) = take(2_usize)(i)?; // reserved + let (i, param_cnt) = le_u16(i)?; + let (i, param_offset) = le_u16(i)?; + let (i, data_cnt) = le_u16(i)?; + let (i, data_offset) = le_u16(i)?; + let (i, setup_cnt) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, pipe) = cond(wct == 16 && setup_cnt == 2 && data_cnt > 0, parse_smb_trans_request_record_pipe)(i)?; + let (i, bcc) = le_u16(i)?; + let params = SmbRecordTransRequestParams { + max_data_cnt, + param_cnt, + param_offset, + data_cnt, + data_offset, + bcc + }; + Ok((i, (params, pipe))) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordTransRequestData<'a> { + pub data: &'a[u8], +} + +pub fn parse_smb_trans_request_record_data(i: &[u8], + pad1: usize, param_cnt: u16, pad2: usize, data_len: u16) + -> IResult<&[u8], SmbRecordTransRequestData, SmbError> +{ + let (i, _) = take(pad1)(i)?; + let (i, _) = take(param_cnt)(i)?; + let (i, _) = take(pad2)(i)?; + let (i, data) = take(data_len)(i)?; + let req = SmbRecordTransRequestData { data }; + Ok((i, req)) +} + +pub fn parse_smb_trans_request_record<'a>(i: &'a[u8], r: &SmbRecord) + -> IResult<&'a[u8], SmbRecordTransRequest<'a>, SmbError> +{ + let (rem, (params, pipe)) = parse_smb_trans_request_record_params(i)?; + let mut offset = 32 + (i.len() - rem.len()); // init with SMB header + SCLogDebug!("params {:?}: offset {}", params, offset); + + let (rem2, n) = smb1_get_string(rem, r, offset)?; + offset += rem.len() - rem2.len(); + SCLogDebug!("n {:?}: offset {}", n, offset); + + // spec says pad to 4 bytes, but traffic shows this doesn't + // always happen. + let pad1 = if offset == params.param_offset as usize || + offset == params.data_offset as usize { + 0 + } else { + offset % 4 + }; + SCLogDebug!("pad1 {}", pad1); + offset += pad1; + offset += params.param_cnt as usize; + + let recdata = if params.data_cnt > 0 { + // ignore padding rule if we're already at the correct + // offset. + let pad2 = if offset == params.data_offset as usize { + 0 + } else { + offset % 4 + }; + SCLogDebug!("pad2 {}", pad2); + + let d = match parse_smb_trans_request_record_data(rem2, + pad1, params.param_cnt, pad2, params.data_cnt) { + Ok((_, rd)) => rd, + Err(e) => { return Err(e); } + }; + SCLogDebug!("d {:?}", d); + d + } else { + SmbRecordTransRequestData { data: &[], } // no data + }; + + let res = SmbRecordTransRequest { + params, pipe, txname: n, data: recdata, + }; + Ok((rem, res)) +} + + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordTransResponse<'a> { + pub data_cnt: u16, + pub bcc: u16, + pub data: &'a[u8], +} + +pub fn parse_smb_trans_response_error_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> { + let (i, _wct) = le_u8(i)?; + let (i, bcc) = le_u16(i)?; + let resp = SmbRecordTransResponse { + data_cnt: 0, + bcc, + data: &[], + }; + Ok((i, resp)) +} + +pub fn parse_smb_trans_response_regular_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> { + let (i, wct) = le_u8(i)?; + let (i, _total_param_cnt) = le_u16(i)?; + let (i, _total_data_count) = le_u16(i)?; + let (i, _) = take(2_usize)(i)?; // reserved + let (i, _param_cnt) = le_u16(i)?; + let (i, _param_offset) = le_u16(i)?; + let (i, _param_displacement) = le_u16(i)?; + let (i, data_cnt) = le_u16(i)?; + let (i, data_offset) = le_u16(i)?; + let (i, _data_displacement) = le_u16(i)?; + let (i, _setup_cnt) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, bcc) = le_u16(i)?; + let (i, _) = take(1_usize)(i)?; // padding + let (i, _padding_evasion) = cond( + data_offset > 36+2*(wct as u16), + |b| take(data_offset - (36+2*(wct as u16)))(b) + )(i)?; + let (i, data) = take(data_cnt)(i)?; + let resp = SmbRecordTransResponse { + data_cnt, + bcc, + data + }; + Ok((i, resp)) +} + +pub fn parse_smb_trans_response_record(i: &[u8]) -> IResult<&[u8], SmbRecordTransResponse> { + let (i, wct) = peek(le_u8)(i)?; + match wct { + 0 => parse_smb_trans_response_error_record(i), + _ => parse_smb_trans_response_regular_record(i), + } +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecordSetupAndX<'a> { + pub sec_blob: &'a[u8], +} + +pub fn parse_smb_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbRecordSetupAndX> { + let (i, _skip1) = take(15_usize)(i)?; + let (i, sec_blob_len) = le_u16(i)?; + let (i, _skip2) = take(8_usize)(i)?; + let (i, _bcc) = le_u16(i)?; + let (i, sec_blob) = take(sec_blob_len)(i)?; + let record = SmbRecordSetupAndX { sec_blob }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbResponseRecordSetupAndX<'a> { + pub sec_blob: &'a[u8], +} + +fn response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> { + let (i, _skip1) = take(7_usize)(i)?; + let (i, sec_blob_len) = le_u16(i)?; + let (i, _bcc) = le_u16(i)?; + let (i, sec_blob) = take(sec_blob_len)(i)?; + let record = SmbResponseRecordSetupAndX { sec_blob }; + Ok((i, record)) +} + +fn response_setup_andx_wct3_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> { + let (i, _skip1) = take(7_usize)(i)?; + let (i, _bcc) = le_u16(i)?; + let record = SmbResponseRecordSetupAndX { + sec_blob: &[], + }; + Ok((i, record)) +} + +fn response_setup_andx_error_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> { + let (i, _wct) = le_u8(i)?; + let (i, _bcc) = le_u16(i)?; + let record = SmbResponseRecordSetupAndX { + sec_blob: &[], + }; + Ok((i, record)) +} + +pub fn parse_smb_response_setup_andx_record(i: &[u8]) -> IResult<&[u8], SmbResponseRecordSetupAndX> { + let (i, wct) = peek(le_u8)(i)?; + match wct { + 0 => response_setup_andx_error_record(i), + 3 => response_setup_andx_wct3_record(i), + _ => response_setup_andx_record(i), + } +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRequestReadAndXRecord<'a> { + pub fid: &'a[u8], + pub size: u64, + pub offset: u64, +} + +pub fn parse_smb_read_andx_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestReadAndXRecord> { + let (i, wct) = le_u8(i)?; + let (i, _andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _andx_offset) = le_u16(i)?; + let (i, fid) = take(2_usize)(i)?; + let (i, offset) = le_u32(i)?; + let (i, max_count_low) = le_u16(i)?; + let (i, _) = take(2_usize)(i)?; + let (i, max_count_high) = le_u32(i)?; + let (i, _) = take(2_usize)(i)?; + let (i, high_offset) = cond(wct == 12,le_u32)(i)?; // only from wct ==12? + let record = SmbRequestReadAndXRecord { + fid, + size: (((max_count_high as u64) << 16)|max_count_low as u64), + offset: high_offset.map(|ho| (ho as u64) << 32 | offset as u64).unwrap_or(0), + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbResponseReadAndXRecord<'a> { + pub len: u32, + pub data: &'a[u8], +} + +pub fn parse_smb_read_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseReadAndXRecord> { + let (i, wct) = le_u8(i)?; + let (i, _andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _andx_offset) = le_u16(i)?; + let (i, _) = take(6_usize)(i)?; + let (i, data_len_low) = le_u16(i)?; + let (i, data_offset) = le_u16(i)?; + let (i, data_len_high) = le_u32(i)?; + let (i, _) = take(6_usize)(i)?; // reserved + let (i, bcc) = le_u16(i)?; + let (i, _padding) = cond( + bcc > data_len_low, + |b| take(bcc - data_len_low)(b) + )(i)?; // TODO figure out how this works with data_len_high + let (i, _padding_evasion) = cond( + data_offset > 36+2*(wct as u16), + |b| take(data_offset - (36+2*(wct as u16)))(b) + )(i)?; + let (i, file_data) = rest(i)?; + + let record = SmbResponseReadAndXRecord { + len: ((data_len_high << 16)|data_len_low as u32), + data: file_data, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRequestRenameRecord { + pub oldname: Vec<u8>, + pub newname: Vec<u8>, +} + +pub fn parse_smb_rename_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestRenameRecord, SmbError> { + let (i, _wct) = le_u8(i)?; + let (i, _search_attr) = le_u16(i)?; + let (i, _bcc) = le_u16(i)?; + let (i, _oldtype) = le_u8(i)?; + let (i, oldname) = smb_get_unicode_string(i)?; + let (i, _newtype) = le_u8(i)?; + let (i, newname) = smb_get_unicode_string_with_offset(i, 1)?; // HACK if we assume oldname is a series of utf16 chars offset would be 1 + let record = SmbRequestRenameRecord { + oldname, + newname + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRequestCreateAndXRecord<> { + pub disposition: u32, + pub create_options: u32, + pub file_name: Vec<u8>, +} + +pub fn parse_smb_create_andx_request_record<'a>(i: &'a[u8], r: &SmbRecord) + -> IResult<&'a[u8], SmbRequestCreateAndXRecord<>, SmbError> +{ + let (i, _skip1) = take(6_usize)(i)?; + let (i, file_name_len) = le_u16(i)?; + let (i, _skip3) = take(28_usize)(i)?; + let (i, disposition) = le_u32(i)?; + let (i, create_options) = le_u32(i)?; + let (i, _skip2) = take(5_usize)(i)?; + let (i, bcc) = le_u16(i)?; + let (i, file_name) = cond( + bcc >= file_name_len, + |b| smb1_get_string(b, r, (bcc - file_name_len) as usize) + )(i)?; + let (i, _skip3) = rest(i)?; + let record = SmbRequestCreateAndXRecord { + disposition, + create_options, + file_name: file_name.unwrap_or_default(), + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Trans2RecordParamSetFileInfoDisposition<> { + pub delete: bool, +} + +pub fn parse_trans2_request_data_set_file_info_disposition(i: &[u8]) + -> IResult<&[u8], Trans2RecordParamSetFileInfoDisposition> { + let (i, delete) = le_u8(i)?; + let record = Trans2RecordParamSetFileInfoDisposition { + delete: delete & 1 == 1, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Trans2RecordParamSetFileInfo<'a> { + pub fid: &'a[u8], + pub loi: u16, +} + +pub fn parse_trans2_request_params_set_file_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfo> { + let (i, fid) = take(2_usize)(i)?; + let (i, loi) = le_u16(i)?; + let record = Trans2RecordParamSetFileInfo { fid, loi }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Trans2RecordParamSetFileInfoRename<'a> { + pub replace: bool, + pub newname: &'a[u8], +} + +pub fn parse_trans2_request_data_set_file_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetFileInfoRename> { + let (i, replace) = le_u8(i)?; + let (i, _reserved) = take(3_usize)(i)?; + let (i, _root_dir) = take(4_usize)(i)?; + let (i, newname_len) = le_u32(i)?; + let (i, newname) = take(newname_len)(i)?; + let record = Trans2RecordParamSetFileInfoRename { + replace: replace==1, + newname, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Trans2RecordParamSetPathInfo<> { + pub loi: u16, + pub oldname: Vec<u8>, +} + +pub fn parse_trans2_request_params_set_path_info(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfo, SmbError> { + let (i, loi) = le_u16(i)?; + let (i, _reserved) = take(4_usize)(i)?; + let (i, oldname) = smb_get_unicode_string(i)?; + let record = Trans2RecordParamSetPathInfo { loi, oldname }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct Trans2RecordParamSetPathInfoRename<'a> { + pub replace: bool, + pub newname: &'a[u8], +} + +pub fn parse_trans2_request_data_set_path_info_rename(i: &[u8]) -> IResult<&[u8], Trans2RecordParamSetPathInfoRename> { + let (i, replace) = le_u8(i)?; + let (i, _reserved) = take(3_usize)(i)?; + let (i, _root_dir) = take(4_usize)(i)?; + let (i, newname_len) = le_u32(i)?; + let (i, newname) = take(newname_len)(i)?; + let record = Trans2RecordParamSetPathInfoRename { + replace: replace==1, + newname + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRequestTrans2Record<'a> { + pub subcmd: u16, + pub setup_blob: &'a[u8], + pub data_blob: &'a[u8], +} + +pub fn parse_smb_trans2_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestTrans2Record> { + let (i, _wct) = le_u8(i)?; + let (i, _total_param_cnt) = le_u16(i)?; + let (i, _total_data_cnt) = le_u16(i)?; + let (i, _max_param_cnt) = le_u16(i)?; + let (i, _max_data_cnt) = le_u16(i)?; + let (i, _max_setup_cnt) = le_u8(i)?; + let (i, _reserved1) = take(1_usize)(i)?; + let (i, _flags) = le_u16(i)?; + let (i, _timeout) = le_u32(i)?; + let (i, _reserved2) = take(2_usize)(i)?; + let (i, param_cnt) = le_u16(i)?; + let (i, param_offset) = verify(le_u16, |&v| v <= (u16::MAX - param_cnt))(i)?; + let (i, data_cnt) = le_u16(i)?; + let (i, data_offset) = le_u16(i)?; + let (i, _setup_cnt) = le_u8(i)?; + let (i, _reserved3) = take(1_usize)(i)?; + let (i, subcmd) = le_u16(i)?; + let (i, _bcc) = le_u16(i)?; + //TODO test and use param_offset + let (i, _padding) = take(3_usize)(i)?; + let (i, setup_blob) = take(param_cnt)(i)?; + let (i, _padding2) = cond( + data_offset > param_offset + param_cnt, + |b| take(data_offset - param_offset - param_cnt)(b) + )(i)?; + let (i, data_blob) = take(data_cnt)(i)?; + + let record = SmbRequestTrans2Record { + subcmd, + setup_blob, + data_blob + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbResponseCreateAndXRecord<'a> { + pub fid: &'a[u8], + pub create_ts: SMBFiletime, + pub last_access_ts: SMBFiletime, + pub last_write_ts: SMBFiletime, + pub last_change_ts: SMBFiletime, + pub file_size: u64, +} + +pub fn parse_smb_create_andx_response_record(i: &[u8]) -> IResult<&[u8], SmbResponseCreateAndXRecord> { + let (i, wct) = le_u8(i)?; + let (i, _andx_command) = le_u8(i)?; + let (i, _) = take(1_usize)(i)?; // reserved + let (i, _andx_offset) = le_u16(i)?; + let (i, _oplock_level) = le_u8(i)?; + let (i, fid) = take(2_usize)(i)?; + let (i, _create_action) = le_u32(i)?; + let (i, create_ts) = le_u64(i)?; + let (i, last_access_ts) = le_u64(i)?; + let (i, last_write_ts) = le_u64(i)?; + let (i, last_change_ts) = le_u64(i)?; + let (i, _) = take(4_usize)(i)?; + let (i, file_size) = le_u64(i)?; + let (i, _eof) = le_u64(i)?; + let (i, _file_type) = le_u16(i)?; + let (i, _ipc_state) = le_u16(i)?; + let (i, _is_dir) = le_u8(i)?; + let (i, _) = cond(wct == 42, take(32_usize))(i)?; + let (i, _bcc) = le_u16(i)?; + let record = SmbResponseCreateAndXRecord { + fid, + create_ts: SMBFiletime::new(create_ts), + last_access_ts: SMBFiletime::new(last_access_ts), + last_write_ts: SMBFiletime::new(last_write_ts), + last_change_ts: SMBFiletime::new(last_change_ts), + file_size, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRequestCloseRecord<'a> { + pub fid: &'a[u8], +} + +pub fn parse_smb1_close_request_record(i: &[u8]) -> IResult<&[u8], SmbRequestCloseRecord> { + let (i, _) = take(1_usize)(i)?; + let (i, fid) = take(2_usize)(i)?; + let record = SmbRequestCloseRecord { + fid, + }; + Ok((i, record)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbVersion<> { + pub version: u8, +} + +pub fn parse_smb_version(i: &[u8]) -> IResult<&[u8], SmbVersion> { + let (i, version) = le_u8(i)?; + let (i, _) = tag(b"SMB")(i)?; + let version = SmbVersion { version }; + Ok((i, version)) +} + +#[derive(Debug,PartialEq, Eq)] +pub struct SmbRecord<'a> { + pub command: u8, + pub is_dos_error: bool, + pub nt_status: u32, + pub flags: u8, + pub flags2: u16, + + pub tree_id: u16, + pub user_id: u16, + pub multiplex_id: u16, + + pub process_id: u32, + pub ssn_id: u32, + + pub data: &'a[u8], +} + +impl<'a> SmbRecord<'a> { + pub fn has_unicode_support(&self) -> bool { + self.flags2 & 0x8000_u16 != 0 + } + + /// Return true if record is a request. + pub fn is_request(&self) -> bool { + self.flags & SMB1_FLAGS_RESPONSE == 0 + } + + /// Return true if record is a reply. + pub fn is_response(&self) -> bool { + self.flags & SMB1_FLAGS_RESPONSE != 0 + } +} + +pub fn parse_smb_record(i: &[u8]) -> IResult<&[u8], SmbRecord> { + let (i, _) = tag(b"\xffSMB")(i)?; + let (i, command) = le_u8(i)?; + let (i, nt_status) = le_u32(i)?; + let (i, flags) = le_u8(i)?; + let (i, flags2) = le_u16(i)?; + let (i, process_id_high) = le_u16(i)?; + let (i, _signature) = take(8_usize)(i)?; + let (i, _reserved) = take(2_usize)(i)?; + let (i, tree_id) = le_u16(i)?; + let (i, process_id) = le_u16(i)?; + let (i, user_id) = le_u16(i)?; + let (i, multiplex_id) = le_u16(i)?; + let (i, data) = rest(i)?; + + let record = SmbRecord { + command, + nt_status, + flags, + flags2, + is_dos_error: (flags2 & 0x4000_u16 == 0),// && nt_status != 0), + tree_id, + user_id, + multiplex_id, + + process_id: (process_id_high as u32) << 16 | process_id as u32, + //ssn_id: (((process_id as u32)<< 16)|(user_id as u32)), + ssn_id: user_id as u32, + data, + }; + Ok((i, record)) +} + +#[test] +fn test_parse_smb1_write_andx_request_record_origin() { + let data = hex::decode("0eff000000014000000000ff00000008001400000014003f000000000014004142434445464748494a4b4c4d4e4f5051520a0a").unwrap(); + let result = parse_smb1_write_andx_request_record(&data, SMB1_HEADER_SIZE); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!(record.offset, 0); + assert_eq!(record.len, 20); + assert_eq!(record.fid, &[0x01, 0x40]); + assert_eq!(record.data.len(), 20); + assert_eq!(record.data, b"ABCDEFGHIJKLMNOPQR\n\n"); +} diff --git a/rust/src/smb/smb1_session.rs b/rust/src/smb/smb1_session.rs new file mode 100644 index 0000000..c39c7ce --- /dev/null +++ b/rust/src/smb/smb1_session.rs @@ -0,0 +1,202 @@ +/* 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. + */ + +use crate::smb::smb_records::*; +use crate::smb::smb1_records::*; +use crate::smb::smb::*; +use crate::smb::events::*; +use crate::smb::auth::*; + +#[derive(Debug)] +pub struct SessionSetupRequest { + pub native_os: Vec<u8>, + pub native_lm: Vec<u8>, + pub primary_domain: Vec<u8>, +} + +#[derive(Debug)] +pub struct SessionSetupResponse { + pub native_os: Vec<u8>, + pub native_lm: Vec<u8>, +} + +pub fn smb1_session_setup_request_host_info(r: &SmbRecord, blob: &[u8]) -> SessionSetupRequest +{ + if blob.len() > 1 && r.has_unicode_support() { + let offset = r.data.len() - blob.len(); + let blob = if offset % 2 == 1 { &blob[1..] } else { blob }; + let (native_os, native_lm, primary_domain) = match smb_get_unicode_string(blob) { + Ok((rem, n1)) => { + match smb_get_unicode_string(rem) { + Ok((rem, n2)) => { + match smb_get_unicode_string(rem) { + Ok((_, n3)) => { (n1, n2, n3) }, + _ => { (n1, n2, Vec::new()) }, + } + }, + _ => { (n1, Vec::new(), Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("name1 {:?} name2 {:?} name3 {:?}", native_os,native_lm,primary_domain); + SessionSetupRequest { + native_os, + native_lm, + primary_domain, + } + } else { + let (native_os, native_lm, primary_domain) = match smb_get_ascii_string(blob) { + Ok((rem, n1)) => { + match smb_get_ascii_string(rem) { + Ok((rem, n2)) => { + match smb_get_ascii_string(rem) { + Ok((_, n3)) => { (n1, n2, n3) }, + _ => { (n1, n2, Vec::new()) }, + } + }, + _ => { (n1, Vec::new(), Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("session_setup_request_host_info: not unicode"); + SessionSetupRequest { + native_os, + native_lm, + primary_domain, + } + } +} + +pub fn smb1_session_setup_response_host_info(r: &SmbRecord, blob: &[u8]) -> SessionSetupResponse +{ + if blob.len() > 1 && r.has_unicode_support() { + let offset = r.data.len() - blob.len(); + let blob = if offset % 2 == 1 { &blob[1..] } else { blob }; + let (native_os, native_lm) = match smb_get_unicode_string(blob) { + Ok((rem, n1)) => { + match smb_get_unicode_string(rem) { + Ok((_, n2)) => (n1, n2), + _ => { (n1, Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new()) }, + }; + + SCLogDebug!("name1 {:?} name2 {:?}", native_os,native_lm); + SessionSetupResponse { + native_os, + native_lm, + } + } else { + SCLogDebug!("session_setup_response_host_info: not unicode"); + let (native_os, native_lm) = match smb_get_ascii_string(blob) { + Ok((rem, n1)) => { + match smb_get_ascii_string(rem) { + Ok((_, n2)) => (n1, n2), + _ => { (n1, Vec::new()) }, + } + }, + _ => { (Vec::new(), Vec::new()) }, + }; + SessionSetupResponse { + native_os, + native_lm, + } + } +} + +pub fn smb1_session_setup_request(state: &mut SMBState, r: &SmbRecord, andx_offset: usize) +{ + SCLogDebug!("SMB1_COMMAND_SESSION_SETUP_ANDX user_id {}", r.user_id); + #[allow(clippy::single_match)] + match parse_smb_setup_andx_record(&r.data[andx_offset-SMB1_HEADER_SIZE..]) { + Ok((rem, setup)) => { + let hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.ssn_id as u64, 0, r.multiplex_id as u64); + let tx = state.new_sessionsetup_tx(hdr); + tx.vercmd.set_smb1_cmd(r.command); + + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + td.request_host = Some(smb1_session_setup_request_host_info(r, rem)); + if let Some(s) = parse_secblob(setup.sec_blob) { + td.ntlmssp = s.ntlmssp; + td.krb_ticket = s.krb; + if let Some(ntlm) = &td.ntlmssp { + if ntlm.warning { + tx.set_event(SMBEvent::UnusualNtlmsspOrder); + } + } + } + } + }, + _ => { + // events.push(SMBEvent::MalformedData); + }, + } +} + +fn smb1_session_setup_update_tx(tx: &mut SMBTransaction, r: &SmbRecord, andx_offset: usize) +{ + match parse_smb_response_setup_andx_record(&r.data[andx_offset-SMB1_HEADER_SIZE..]) { + Ok((rem, _setup)) => { + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + td.response_host = Some(smb1_session_setup_response_host_info(r, rem)); + } + }, + _ => { + tx.set_event(SMBEvent::MalformedData); + }, + } + // update tx even if we can't parse the response + tx.hdr = SMBCommonHdr::from1(r, SMBHDR_TYPE_HEADER); // to overwrite ssn_id 0 + tx.set_status(r.nt_status, r.is_dos_error); + tx.response_done = true; +} + +pub fn smb1_session_setup_response(state: &mut SMBState, r: &SmbRecord, andx_offset: usize) +{ + // try exact match with session id already set (e.g. NTLMSSP AUTH phase) + let found = r.ssn_id != 0 && match state.get_sessionsetup_tx( + SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.ssn_id as u64, 0, r.multiplex_id as u64)) + { + Some(tx) => { + smb1_session_setup_update_tx(tx, r, andx_offset); + SCLogDebug!("smb1_session_setup_response: tx {:?}", tx); + true + }, + None => { false }, + }; + // otherwise try match with ssn id 0 (e.g. NTLMSSP_NEGOTIATE) + if !found { + match state.get_sessionsetup_tx( + SMBCommonHdr::new(SMBHDR_TYPE_HEADER, 0, 0, r.multiplex_id as u64)) + { + Some(tx) => { + smb1_session_setup_update_tx(tx, r, andx_offset); + SCLogDebug!("smb1_session_setup_response: tx {:?}", tx); + }, + None => { + SCLogDebug!("smb1_session_setup_response: tx not found for {:?}", r); + }, + } + } +} diff --git a/rust/src/smb/smb2.rs b/rust/src/smb/smb2.rs new file mode 100644 index 0000000..a2fe021 --- /dev/null +++ b/rust/src/smb/smb2.rs @@ -0,0 +1,913 @@ +/* Copyright (C) 2017-2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use nom7::Err; + +use crate::core::*; + +use crate::smb::smb::*; +use crate::smb::smb2_records::*; +use crate::smb::smb2_session::*; +use crate::smb::smb2_ioctl::*; +use crate::smb::dcerpc::*; +use crate::smb::events::*; +use crate::smb::files::*; +use crate::smb::smb_status::*; + +pub const SMB2_COMMAND_NEGOTIATE_PROTOCOL: u16 = 0; +pub const SMB2_COMMAND_SESSION_SETUP: u16 = 1; +pub const SMB2_COMMAND_SESSION_LOGOFF: u16 = 2; +pub const SMB2_COMMAND_TREE_CONNECT: u16 = 3; +pub const SMB2_COMMAND_TREE_DISCONNECT: u16 = 4; +pub const SMB2_COMMAND_CREATE: u16 = 5; +pub const SMB2_COMMAND_CLOSE: u16 = 6; +pub const SMB2_COMMAND_FLUSH: u16 = 7; +pub const SMB2_COMMAND_READ: u16 = 8; +pub const SMB2_COMMAND_WRITE: u16 = 9; +pub const SMB2_COMMAND_LOCK: u16 = 10; +pub const SMB2_COMMAND_IOCTL: u16 = 11; +pub const SMB2_COMMAND_CANCEL: u16 = 12; +pub const SMB2_COMMAND_KEEPALIVE: u16 = 13; +pub const SMB2_COMMAND_FIND: u16 = 14; +pub const SMB2_COMMAND_CHANGE_NOTIFY: u16 = 15; +pub const SMB2_COMMAND_GET_INFO: u16 = 16; +pub const SMB2_COMMAND_SET_INFO: u16 = 17; +pub const SMB2_COMMAND_OPLOCK_BREAK: u16 = 18; + +pub fn smb2_command_string(c: u16) -> String { + match c { + SMB2_COMMAND_NEGOTIATE_PROTOCOL => "SMB2_COMMAND_NEGOTIATE_PROTOCOL", + SMB2_COMMAND_SESSION_SETUP => "SMB2_COMMAND_SESSION_SETUP", + SMB2_COMMAND_SESSION_LOGOFF => "SMB2_COMMAND_SESSION_LOGOFF", + SMB2_COMMAND_TREE_CONNECT => "SMB2_COMMAND_TREE_CONNECT", + SMB2_COMMAND_TREE_DISCONNECT => "SMB2_COMMAND_TREE_DISCONNECT", + SMB2_COMMAND_CREATE => "SMB2_COMMAND_CREATE", + SMB2_COMMAND_CLOSE => "SMB2_COMMAND_CLOSE", + SMB2_COMMAND_READ => "SMB2_COMMAND_READ", + SMB2_COMMAND_FLUSH => "SMB2_COMMAND_FLUSH", + SMB2_COMMAND_WRITE => "SMB2_COMMAND_WRITE", + SMB2_COMMAND_LOCK => "SMB2_COMMAND_LOCK", + SMB2_COMMAND_IOCTL => "SMB2_COMMAND_IOCTL", + SMB2_COMMAND_CANCEL => "SMB2_COMMAND_CANCEL", + SMB2_COMMAND_KEEPALIVE => "SMB2_COMMAND_KEEPALIVE", + SMB2_COMMAND_FIND => "SMB2_COMMAND_FIND", + SMB2_COMMAND_CHANGE_NOTIFY => "SMB2_COMMAND_CHANGE_NOTIFY", + SMB2_COMMAND_GET_INFO => "SMB2_COMMAND_GET_INFO", + SMB2_COMMAND_SET_INFO => "SMB2_COMMAND_SET_INFO", + SMB2_COMMAND_OPLOCK_BREAK => "SMB2_COMMAND_OPLOCK_BREAK", + _ => { return (c).to_string(); }, + }.to_string() + +} + +pub fn smb2_dialect_string(d: u16) -> String { + match d { + 0x0202 => "2.02", + 0x0210 => "2.10", + 0x0222 => "2.22", + 0x0224 => "2.24", + 0x02ff => "2.??", + 0x0300 => "3.00", + 0x0302 => "3.02", + 0x0310 => "3.10", + 0x0311 => "3.11", + _ => { return (d).to_string(); }, + }.to_string() +} + +// later we'll use this to determine if we need to +// track a ssn per type +fn smb2_create_new_tx(cmd: u16) -> bool { + match cmd { + SMB2_COMMAND_READ | + SMB2_COMMAND_WRITE | + SMB2_COMMAND_GET_INFO | + SMB2_COMMAND_SET_INFO => { false }, + _ => { true }, + } +} + +fn smb2_read_response_record_generic(state: &mut SMBState, r: &Smb2Record) +{ + if smb2_create_new_tx(r.command) { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.get_generic_tx(2, r.command, &tx_hdr); + if let Some(tx) = tx { + tx.set_status(r.nt_status, false); + tx.response_done = true; + } + } +} + +pub fn smb2_read_response_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) +{ + let max_queue_size = unsafe { SMB_CFG_MAX_READ_QUEUE_SIZE }; + let max_queue_cnt = unsafe { SMB_CFG_MAX_READ_QUEUE_CNT }; + + smb2_read_response_record_generic(state, r); + + match parse_smb2_response_read(r.data) { + Ok((_, rd)) => { + if rd.len - rd.data.len() as u32 > nbss_remaining { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::ReadResponseTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + if r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + SCLogDebug!("SMBv2/READ: incomplete record, expecting a follow up"); + // fall through + + } else if r.nt_status != SMB_NTSTATUS_SUCCESS { + SCLogDebug!("SMBv2: read response error code received: skip record"); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + + if (state.max_read_size != 0 && rd.len > state.max_read_size) || + (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.len }) + { + state.set_event(SMBEvent::ReadResponseTooLarge); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + } + + SCLogDebug!("SMBv2: read response => {:?}", rd); + + // get the request info. If we don't have it, there is nothing + // we can do except skip this record. + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let (offset, file_guid) = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => (o.offset, o.guid), + None => { + SCLogDebug!("SMBv2 READ response: reply to unknown request {:?}",rd); + state.set_skip(Direction::ToClient, nbss_remaining); + return; + }, + }; + SCLogDebug!("SMBv2 READ: GUID {:?} offset {}", file_guid, offset); + + let mut set_event_fileoverlap = false; + // look up existing tracker and if we have it update it + let found = match state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToClient) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::ReadQueueSizeExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::ReadQueueCntExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &tdf.file_name, rd.data, offset, + rd.len, false, &file_id); + } + } + true + }, + None => { false }, + }; + SCLogDebug!("existing file tx? {}", found); + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => (n.name.to_vec(), n.is_pipe), + _ => { (Vec::new(), false) }, + }; + let mut is_dcerpc = if is_pipe || share_name.is_empty() { + state.get_service_for_guid(&file_guid).1 + } else { + false + }; + SCLogDebug!("SMBv2/READ: share_name {:?} is_pipe {} is_dcerpc {}", + share_name, is_pipe, is_dcerpc); + + if share_name.is_empty() && !is_pipe { + SCLogDebug!("SMBv2/READ: no tree connect seen, we don't know if we are a pipe"); + + if smb_dcerpc_probe(rd.data) { + SCLogDebug!("SMBv2/READ: looks like dcerpc"); + // insert fake tree to assist in follow up lookups + let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); + state.ssn2tree_map.insert(tree_key, tree); + if !is_dcerpc { + state.guid2name_map.insert(file_guid.to_vec(), b"suricata::dcerpc".to_vec()); + } + is_pipe = true; + is_dcerpc = true; + } else { + SCLogDebug!("SMBv2/READ: not DCERPC"); + } + } + + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC read"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_READ, r.nt_status); + smb_read_dcerpc_record(state, vercmd, hdr, &file_guid, rd.data); + } else if is_pipe { + SCLogDebug!("non-DCERPC pipe"); + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => { n.to_vec() } + None => { b"<unknown>".to_vec() } + }; + + let tx = state.new_file_tx(&file_guid, &file_name, Direction::ToClient); + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_READ); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + tdf.share_name = share_name; + let file_id : u32 = tx.id as u32; + if offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + rd.len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::ReadQueueSizeExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::ReadQueueCntExceeded); + state.set_skip(Direction::ToClient, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, rd.data, offset, + rd.len, false, &file_id); + } + } + } + } + + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + state.set_file_left(Direction::ToClient, rd.len, rd.data.len() as u32, file_guid.to_vec()); + } + _ => { + SCLogDebug!("SMBv2: failed to parse read response"); + state.set_event(SMBEvent::MalformedData); + } + } +} + +pub fn smb2_write_request_record(state: &mut SMBState, r: &Smb2Record, nbss_remaining: u32) +{ + let max_queue_size = unsafe { SMB_CFG_MAX_WRITE_QUEUE_SIZE }; + let max_queue_cnt = unsafe { SMB_CFG_MAX_WRITE_QUEUE_CNT }; + + SCLogDebug!("SMBv2/WRITE: request record"); + if smb2_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_generic_tx(2, r.command, tx_key); + tx.request_done = true; + } + match parse_smb2_request_write(r.data) { + Ok((_, wr)) => { + if wr.wr_len - wr.data.len() as u32 > nbss_remaining { + // Record claims more bytes than are in NBSS record... + state.set_event(SMBEvent::WriteRequestTooLarge); + // Skip the remaining bytes of the record. + state.set_skip(Direction::ToServer, nbss_remaining); + return; + } + if (state.max_write_size != 0 && wr.wr_len > state.max_write_size) || + (unsafe { SMB_CFG_MAX_WRITE_SIZE != 0 && SMB_CFG_MAX_WRITE_SIZE < wr.wr_len }) { + state.set_event(SMBEvent::WriteRequestTooLarge); + state.set_skip(Direction::ToServer, nbss_remaining); + return; + } + + /* update key-guid map */ + let guid_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GUID); + state.ssn2vec_map.insert(guid_key, wr.guid.to_vec()); + + let file_guid = wr.guid.to_vec(); + let file_name = match state.guid2name_map.get(&file_guid) { + Some(n) => n.to_vec(), + None => Vec::new(), + }; + + let mut set_event_fileoverlap = false; + let found = match state.get_file_tx_by_fuid_with_open_file(&file_guid, Direction::ToServer) { + Some(tx) => { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if wr.wr_offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::WriteQueueSizeExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::WriteQueueCntExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, false, &file_id); + } + } + true + }, + None => { false }, + }; + if !found { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + let (share_name, mut is_pipe) = match state.ssn2tree_map.get(&tree_key) { + Some(n) => { (n.name.to_vec(), n.is_pipe) }, + _ => { (Vec::new(), false) }, + }; + let mut is_dcerpc = if is_pipe || share_name.is_empty() { + state.get_service_for_guid(wr.guid).1 + } else { + false + }; + SCLogDebug!("SMBv2/WRITE: share_name {:?} is_pipe {} is_dcerpc {}", + share_name, is_pipe, is_dcerpc); + + // if we missed the TREE connect we can't be sure if 'is_dcerpc' is correct + if share_name.is_empty() && !is_pipe { + SCLogDebug!("SMBv2/WRITE: no tree connect seen, we don't know if we are a pipe"); + + if smb_dcerpc_probe(wr.data) { + SCLogDebug!("SMBv2/WRITE: looks like we have dcerpc"); + + let tree = SMBTree::new(b"suricata::dcerpc".to_vec(), true); + state.ssn2tree_map.insert(tree_key, tree); + if !is_dcerpc { + state.guid2name_map.insert(file_guid.to_vec(), + b"suricata::dcerpc".to_vec()); + } + is_pipe = true; + is_dcerpc = true; + } else { + SCLogDebug!("SMBv2/WRITE: not DCERPC"); + } + } + if is_pipe && is_dcerpc { + SCLogDebug!("SMBv2 DCERPC write"); + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_WRITE); + smb_write_dcerpc_record(state, vercmd, hdr, wr.data); + } else if is_pipe { + SCLogDebug!("non-DCERPC pipe: skip rest of the record"); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + let tx = state.new_file_tx(&file_guid, &file_name, Direction::ToServer); + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_WRITE); + tx.hdr = SMBCommonHdr::new(SMBHDR_TYPE_HEADER, + r.session_id, r.tree_id, 0); // TODO move into new_file_tx + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + let file_id : u32 = tx.id as u32; + if wr.wr_offset < tdf.file_tracker.tracked { + set_event_fileoverlap = true; + } + + if max_queue_size != 0 && tdf.file_tracker.get_inflight_size() + wr.wr_len as u64 > max_queue_size.into() { + state.set_event(SMBEvent::WriteQueueSizeExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else if max_queue_cnt != 0 && tdf.file_tracker.get_inflight_cnt() >= max_queue_cnt as usize { + state.set_event(SMBEvent::WriteQueueCntExceeded); + state.set_skip(Direction::ToServer, nbss_remaining); + } else { + filetracker_newchunk(&mut tdf.file_tracker, + &file_name, wr.data, wr.wr_offset, + wr.wr_len, false, &file_id); + } + } + } + } + + if set_event_fileoverlap { + state.set_event(SMBEvent::FileOverlap); + } + state.set_file_left(Direction::ToServer, wr.wr_len, wr.data.len() as u32, file_guid.to_vec()); + }, + _ => { + state.set_event(SMBEvent::MalformedData); + }, + } +} + +pub fn smb2_request_record(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMBv2 request record, command {} tree {} session {}", + &smb2_command_string(r.command), r.tree_id, r.session_id); + + let mut events : Vec<SMBEvent> = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_SET_INFO => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", r); + let have_si_tx = match parse_smb2_request_setinfo(r.data) { + Ok((_, rd)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", rd); + + match rd.data { + Smb2SetInfoRequestData::RENAME(ref ren) => { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let mut newname = ren.name.to_vec(); + newname.retain(|&i|i != 0x00); + let oldname = match state.guid2name_map.get(rd.guid) { + Some(n) => { n.to_vec() }, + None => { b"<unknown>".to_vec() }, + }; + let tx = state.new_rename_tx(rd.guid.to_vec(), oldname, newname); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); + true + } + Smb2SetInfoRequestData::DISPOSITION(ref dis) => { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let fname = match state.guid2name_map.get(rd.guid) { + Some(n) => { n.to_vec() }, + None => { + // try to find latest created file in case of chained commands + let mut guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + if guid_key.msg_id == 0 { + b"<unknown>".to_vec() + } else { + guid_key.msg_id -= 1; + match state.ssn2vec_map.get(&guid_key) { + Some(n) => { n.to_vec() }, + None => { b"<unknown>".to_vec()}, + } + } + }, + }; + let tx = state.new_setfileinfo_tx(fname, rd.guid.to_vec(), rd.class as u16, rd.infolvl as u16, dis.delete); + tx.hdr = tx_hdr; + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_SET_INFO); + true + } + _ => false, + } + }, + Err(Err::Incomplete(_n)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _n); + events.push(SMBEvent::MalformedData); + false + }, + Err(Err::Error(_e)) | + Err(Err::Failure(_e)) => { + SCLogDebug!("SMB2_COMMAND_SET_INFO: {:?}", _e); + events.push(SMBEvent::MalformedData); + false + }, + }; + have_si_tx + }, + SMB2_COMMAND_IOCTL => { + smb2_ioctl_request_record(state, r); + true + }, + SMB2_COMMAND_TREE_DISCONNECT => { + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + match parse_smb2_request_negotiate_protocol(r.data) { + Ok((_, rd)) => { + let mut dialects : Vec<Vec<u8>> = Vec::new(); + for d in rd.dialects_vec { + SCLogDebug!("dialect {:x} => {}", d, &smb2_dialect_string(d)); + let dvec = smb2_dialect_string(d).as_bytes().to_vec(); + dialects.push(dvec); + } + + let found = match state.get_negotiate_tx(2) { + Some(_) => { + SCLogDebug!("WEIRD, should not have NEGOTIATE tx!"); + true + }, + None => { false }, + }; + if !found { + let tx = state.new_negotiate_tx(2); + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.dialects2 = dialects; + tdn.client_guid = Some(rd.client_guid.to_vec()); + } + tx.request_done = true; + } + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_SESSION_SETUP => { + smb2_session_setup_request(state, r); + true + }, + SMB2_COMMAND_TREE_CONNECT => { + match parse_smb2_request_tree_connect(r.data) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut name_val = tr.share_name.to_vec(); + name_val.retain(|&i|i != 0x00); + if name_val.len() > 1 { + name_val = name_val[1..].to_vec(); + } + + let tx = state.new_treeconnect_tx(name_key, name_val); + tx.request_done = true; + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_TREE_CONNECT); + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_READ => { + match parse_smb2_request_read(r.data) { + Ok((_, rd)) => { + if (state.max_read_size != 0 && rd.rd_len > state.max_read_size) || + (unsafe { SMB_CFG_MAX_READ_SIZE != 0 && SMB_CFG_MAX_READ_SIZE < rd.rd_len }) { + events.push(SMBEvent::ReadRequestTooLarge); + } else { + SCLogDebug!("SMBv2 READ: GUID {:?} requesting {} bytes at offset {}", + rd.guid, rd.rd_len, rd.rd_offset); + + // store read guid,offset in map + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let guidoff = SMBFileGUIDOffset::new(rd.guid.to_vec(), rd.rd_offset); + state.ssn2vecoffset_map.insert(guid_key, guidoff); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + SMB2_COMMAND_CREATE => { + match parse_smb2_request_create(r.data) { + Ok((_, cr)) => { + let del = cr.create_options & 0x0000_1000 != 0; + let dir = cr.create_options & 0x0000_0001 != 0; + + SCLogDebug!("create_options {:08x}", cr.create_options); + + let name_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + state.ssn2vec_map.insert(name_key, cr.data.to_vec()); + + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_create_tx(cr.data, + cr.disposition, del, dir, tx_hdr); + tx.vercmd.set_smb2_cmd(r.command); + SCLogDebug!("TS CREATE TX {} created", tx.id); + true + }, + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + }, + SMB2_COMMAND_WRITE => { + smb2_write_request_record(state, r, 0); + true // write handling creates both file tx and generic tx + }, + SMB2_COMMAND_CLOSE => { + match parse_smb2_request_close(r.data) { + Ok((_, cd)) => { + let found_ts = match state.get_file_tx_by_fuid(cd.guid, Direction::ToServer) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + let found_tc = match state.get_file_tx_by_fuid(cd.guid, Direction::ToClient) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.request_done = true; + tx.response_done = true; + tx.set_status(SMB_NTSTATUS_SUCCESS, false); + true + }, + None => { false }, + }; + if !found_ts && !found_tc { + SCLogDebug!("SMBv2: CLOSE(TS): no TX at GUID {:?}", cd.guid); + } + }, + _ => { + events.push(SMBEvent::MalformedData); + }, + } + false + }, + _ => { + false + }, + }; + /* if we don't have a tx, create it here (maybe) */ + if !have_tx && smb2_create_new_tx(r.command) { + let tx_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + let tx = state.new_generic_tx(2, r.command, tx_key); + SCLogDebug!("TS TX {} command {} created with session_id {} tree_id {} message_id {}", + tx.id, r.command, r.session_id, r.tree_id, r.message_id); + tx.set_events(events); + } +} + +pub fn smb2_response_record(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMBv2 response record, command {} status {} tree {} session {} message {}", + &smb2_command_string(r.command), r.nt_status, + r.tree_id, r.session_id, r.message_id); + + let mut events : Vec<SMBEvent> = Vec::new(); + + let have_tx = match r.command { + SMB2_COMMAND_IOCTL => { + smb2_ioctl_response_record(state, r); + true + }, + SMB2_COMMAND_SESSION_SETUP => { + smb2_session_setup_response(state, r); + true + }, + SMB2_COMMAND_WRITE => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_write(r.data) + { + Ok((_, _wr)) => { + SCLogDebug!("SMBv2: Write response => {:?}", _wr); + + /* search key-guid map */ + let guid_key = SMBCommonHdr::new(SMBHDR_TYPE_GUID, + r.session_id, r.tree_id, r.message_id); + let _guid_vec = match state.ssn2vec_map.remove(&guid_key) { + Some(p) => p, + None => { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + Vec::new() + }, + }; + SCLogDebug!("SMBv2 write response for GUID {:?}", _guid_vec); + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + } + false // the request may have created a generic tx, so handle that here + }, + SMB2_COMMAND_READ => { + if r.nt_status == SMB_NTSTATUS_SUCCESS || + r.nt_status == SMB_NTSTATUS_BUFFER_OVERFLOW { + smb2_read_response_record(state, r, 0); + false + + } else if r.nt_status == SMB_NTSTATUS_END_OF_FILE { + SCLogDebug!("SMBv2: read response => EOF"); + + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_OFFSET); + let file_guid = match state.ssn2vecoffset_map.remove(&guid_key) { + Some(o) => o.guid, + _ => { + SCLogDebug!("SMBv2 READ response: reply to unknown request"); + Vec::new() + }, + }; + let found = match state.get_file_tx_by_fuid(&file_guid, Direction::ToClient) { + Some(tx) => { + if !tx.request_done { + if let Some(SMBTransactionTypeData::FILE(ref mut tdf)) = tx.type_data { + filetracker_close(&mut tdf.file_tracker); + } + } + tx.set_status(r.nt_status, false); + tx.request_done = true; + false + }, + None => { false }, + }; + if !found { + SCLogDebug!("SMBv2 READ: no TX at GUID {:?}", file_guid); + } + false + } else { + SCLogDebug!("SMBv2 READ: status {}", r.nt_status); + false + } + }, + SMB2_COMMAND_CREATE => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_create(r.data) { + Ok((_, cr)) => { + SCLogDebug!("SMBv2: Create response => {:?}", cr); + + let guid_key = SMBCommonHdr::from2_notree(r, SMBHDR_TYPE_FILENAME); + if let Some(mut p) = state.ssn2vec_map.remove(&guid_key) { + p.retain(|&i|i != 0x00); + state.guid2name_map.insert(cr.guid.to_vec(), p); + } else { + SCLogDebug!("SMBv2 response: GUID NOT FOUND"); + } + + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + if let Some(tx) = state.get_generic_tx(2, r.command, &tx_hdr) { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, r.command, &smb2_command_string(r.command)); + tx.set_status(r.nt_status, false); + tx.response_done = true; + + if let Some(SMBTransactionTypeData::CREATE(ref mut tdn)) = tx.type_data { + tdn.create_ts = cr.create_ts.as_unix(); + tdn.last_access_ts = cr.last_access_ts.as_unix(); + tdn.last_write_ts = cr.last_write_ts.as_unix(); + tdn.last_change_ts = cr.last_change_ts.as_unix(); + tdn.size = cr.size; + tdn.guid = cr.guid.to_vec(); + } + } + } + _ => { + events.push(SMBEvent::MalformedData); + }, + } + true + } else { + false + } + }, + SMB2_COMMAND_TREE_DISCONNECT => { + // normally removed when processing request, + // but in case we missed that try again here + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.remove(&tree_key); + false + } + SMB2_COMMAND_TREE_CONNECT => { + if r.nt_status == SMB_NTSTATUS_SUCCESS { + match parse_smb2_response_tree_connect(r.data) { + Ok((_, tr)) => { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let mut share_name = Vec::new(); + let is_pipe = tr.share_type == 2; + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + if let Some(SMBTransactionTypeData::TREECONNECT(ref mut tdn)) = tx.type_data { + tdn.share_type = tr.share_type; + tdn.is_pipe = is_pipe; + tdn.tree_id = r.tree_id; + share_name = tdn.share_name.to_vec(); + } + // update hdr now that we have a tree_id + tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + if found { + let tree = SMBTree::new(share_name.to_vec(), is_pipe); + let tree_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_SHARE); + state.ssn2tree_map.insert(tree_key, tree); + } + true + } + _ => { + events.push(SMBEvent::MalformedData); + false + }, + } + } else { + let name_key = SMBCommonHdr::from2(r, SMBHDR_TYPE_TREE); + let found = match state.get_treeconnect_tx(name_key) { + Some(tx) => { + tx.response_done = true; + tx.set_status(r.nt_status, false); + true + }, + None => { false }, + }; + found + } + }, + SMB2_COMMAND_NEGOTIATE_PROTOCOL => { + let res = if r.nt_status == SMB_NTSTATUS_SUCCESS { + parse_smb2_response_negotiate_protocol(r.data) + } else { + parse_smb2_response_negotiate_protocol_error(r.data) + }; + match res { + Ok((_, rd)) => { + SCLogDebug!("SERVER dialect => {}", &smb2_dialect_string(rd.dialect)); + + let smb_cfg_max_read_size = unsafe { SMB_CFG_MAX_READ_SIZE }; + if smb_cfg_max_read_size != 0 && rd.max_read_size > smb_cfg_max_read_size { + state.set_event(SMBEvent::NegotiateMaxReadSizeTooLarge); + } + let smb_cfg_max_write_size = unsafe { SMB_CFG_MAX_WRITE_SIZE }; + if smb_cfg_max_write_size != 0 && rd.max_write_size > smb_cfg_max_write_size { + state.set_event(SMBEvent::NegotiateMaxWriteSizeTooLarge); + } + + state.dialect = rd.dialect; + state.max_read_size = rd.max_read_size; + state.max_write_size = rd.max_write_size; + + let found2 = match state.get_negotiate_tx(2) { + Some(tx) => { + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.server_guid = rd.server_guid.to_vec(); + } + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { false }, + }; + // SMB2 response to SMB1 request? + let found1 = !found2 && match state.get_negotiate_tx(1) { + Some(tx) => { + if let Some(SMBTransactionTypeData::NEGOTIATE(ref mut tdn)) = tx.type_data { + tdn.server_guid = rd.server_guid.to_vec(); + } + tx.set_status(r.nt_status, false); + tx.response_done = true; + true + }, + None => { false }, + }; + found1 || found2 + }, + _ => { + events.push(SMBEvent::MalformedData); + false + } + } + }, + _ => { + SCLogDebug!("default case: no TX"); + false + }, + }; + if !have_tx { + let tx_hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_GENERICTX); + SCLogDebug!("looking for TX {} with session_id {} tree_id {} message_id {}", + &smb2_command_string(r.command), + r.session_id, r.tree_id, r.message_id); + let _found = match state.get_generic_tx(2, r.command, &tx_hdr) { + Some(tx) => { + SCLogDebug!("tx {} with {}/{} marked as done", + tx.id, r.command, &smb2_command_string(r.command)); + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + tx.set_status(r.nt_status, false); + tx.set_events(events); + true + }, + _ => { + SCLogDebug!("no tx found for {:?}", r); + false + }, + }; + } +} diff --git a/rust/src/smb/smb2_ioctl.rs b/rust/src/smb/smb2_ioctl.rs new file mode 100644 index 0000000..73687aa --- /dev/null +++ b/rust/src/smb/smb2_ioctl.rs @@ -0,0 +1,133 @@ +/* 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. + */ + +use crate::smb::smb::*; +use crate::smb::smb2::*; +use crate::smb::smb2_records::*; +use crate::smb::dcerpc::*; +use crate::smb::events::*; +#[cfg(feature = "debug")] +use crate::smb::funcs::*; +use crate::smb::smb_status::*; + +#[derive(Debug)] +pub struct SMBTransactionIoctl { + pub func: u32, +} + +impl SMBTransactionIoctl { + pub fn new(func: u32) -> Self { + return Self { + func, + }; + } +} + +impl SMBState { + pub fn new_ioctl_tx(&mut self, hdr: SMBCommonHdr, func: u32) + -> &mut SMBTransaction + { + let mut tx = self.new_tx(); + tx.hdr = hdr; + tx.type_data = Some(SMBTransactionTypeData::IOCTL( + SMBTransactionIoctl::new(func))); + tx.request_done = true; + tx.response_done = self.tc_trunc; // no response expected if tc is truncated + + SCLogDebug!("SMB: TX IOCTL created: ID {} FUNC {:08x}: {}", + tx.id, func, &fsctl_func_to_string(func)); + self.transactions.push_back(tx); + let tx_ref = self.transactions.back_mut(); + return tx_ref.unwrap(); + } +} + +// IOCTL responses ASYNC don't set the tree id +pub fn smb2_ioctl_request_record(state: &mut SMBState, r: &Smb2Record) +{ + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + match parse_smb2_request_ioctl(r.data) { + Ok((_, rd)) => { + SCLogDebug!("IOCTL request data: {:?}", rd); + let is_dcerpc = if rd.is_pipe { + state.get_service_for_guid(rd.guid).1 + } else { + false + }; + if is_dcerpc { + SCLogDebug!("IOCTL request data is_pipe. Calling smb_write_dcerpc_record"); + let vercmd = SMBVerCmdStat::new2(SMB2_COMMAND_IOCTL); + smb_write_dcerpc_record(state, vercmd, hdr, rd.data); + } else { + SCLogDebug!("IOCTL {:08x} {}", rd.function, &fsctl_func_to_string(rd.function)); + let tx = state.new_ioctl_tx(hdr, rd.function); + tx.vercmd.set_smb2_cmd(SMB2_COMMAND_IOCTL); + } + }, + _ => { + let tx = state.new_generic_tx(2, r.command, hdr); + tx.set_event(SMBEvent::MalformedData); + }, + }; +} + +// IOCTL responses ASYNC don't set the tree id +pub fn smb2_ioctl_response_record(state: &mut SMBState, r: &Smb2Record) +{ + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + match parse_smb2_response_ioctl(r.data) { + Ok((_, rd)) => { + SCLogDebug!("IOCTL response data: {:?}", rd); + + let is_dcerpc = if rd.is_pipe { + state.get_service_for_guid(rd.guid).1 + } else { + false + }; + if is_dcerpc { + SCLogDebug!("IOCTL response data is_pipe. Calling smb_read_dcerpc_record"); + let vercmd = SMBVerCmdStat::new2_with_ntstatus(SMB2_COMMAND_IOCTL, r.nt_status); + SCLogDebug!("TODO passing empty GUID"); + smb_read_dcerpc_record(state, vercmd, hdr, &[],rd.data); + } else { + SCLogDebug!("SMB2_COMMAND_IOCTL/SMB_NTSTATUS_PENDING looking for {:?}", hdr); + if let Some(tx) = state.get_generic_tx(2, SMB2_COMMAND_IOCTL, &hdr) { + tx.set_status(r.nt_status, false); + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + } + } + }, + _ => { + SCLogDebug!("SMB2_COMMAND_IOCTL/SMB_NTSTATUS_PENDING looking for {:?}", hdr); + if let Some(tx) = state.get_generic_tx(2, SMB2_COMMAND_IOCTL, &hdr) { + SCLogDebug!("updated status of tx {}", tx.id); + tx.set_status(r.nt_status, false); + if r.nt_status != SMB_NTSTATUS_PENDING { + tx.response_done = true; + } + + // parsing failed for 'SUCCESS' record, set event + if r.nt_status == SMB_NTSTATUS_SUCCESS { + SCLogDebug!("parse fail {:?}", r); + tx.set_event(SMBEvent::MalformedData); + } + } + }, + }; +} diff --git a/rust/src/smb/smb2_records.rs b/rust/src/smb/smb2_records.rs new file mode 100644 index 0000000..4a7721c --- /dev/null +++ b/rust/src/smb/smb2_records.rs @@ -0,0 +1,903 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::smb::nbss_records::NBSS_MSGTYPE_SESSION_MESSAGE; +use crate::smb::smb::*; +use nom7::bytes::streaming::{tag, take}; +use nom7::combinator::{cond, map_parser, rest}; +use nom7::error::{make_error, ErrorKind}; +use nom7::multi::count; +use nom7::number::streaming::{le_u16, le_u32, le_u64, le_u8}; +use nom7::{Err, IResult, Needed}; + +const SMB2_FLAGS_SERVER_TO_REDIR: u32 = 0x0000_0001; +const SMB2_FLAGS_ASYNC_COMMAND: u32 = 0x0000_0002; + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2SecBlobRecord<'a> { + pub data: &'a [u8], +} + +pub fn parse_smb2_sec_blob(i: &[u8]) -> IResult<&[u8], Smb2SecBlobRecord> { + let (i, data) = rest(i)?; + Ok((i, Smb2SecBlobRecord { data })) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2RecordDir { + pub request: bool, +} + +pub fn parse_smb2_record_direction(i: &[u8]) -> IResult<&[u8], Smb2RecordDir> { + let (i, _server_component) = tag(b"\xfeSMB")(i)?; + let (i, _skip) = take(12_usize)(i)?; + let (i, flags) = le_u8(i)?; + let record = Smb2RecordDir { + request: flags & 0x01 == 0, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2Record<'a> { + pub direction: u8, // 0 req, 1 res + pub header_len: u16, + pub nt_status: u32, + pub command: u16, + pub message_id: u64, + pub tree_id: u32, + pub async_id: u64, + pub session_id: u64, + pub data: &'a [u8], +} + +impl<'a> Smb2Record<'a> { + pub fn is_request(&self) -> bool { + self.direction == 0 + } + + pub fn is_response(&self) -> bool { + self.direction == 1 + } +} + +#[derive(Debug)] +struct SmbFlags { + direction: u8, + async_command: u8, +} + +fn parse_smb2_flags(i: &[u8]) -> IResult<&[u8], SmbFlags> { + let (i, val) = le_u32(i)?; + let direction = u8::from(val & SMB2_FLAGS_SERVER_TO_REDIR != 0); + let async_command = u8::from(val & SMB2_FLAGS_ASYNC_COMMAND != 0); + Ok(( + i, + SmbFlags { + direction, + async_command, + }, + )) +} + +pub fn parse_smb2_request_record(i: &[u8]) -> IResult<&[u8], Smb2Record> { + let (i, _server_component) = tag(b"\xfeSMB")(i)?; + let (i, hlen) = le_u16(i)?; + let (i, _credit_charge) = le_u16(i)?; + let (i, _channel_seq) = le_u16(i)?; + let (i, _reserved) = take(2_usize)(i)?; + let (i, command) = le_u16(i)?; + let (i, _credits_requested) = le_u16(i)?; + let (i, flags) = parse_smb2_flags(i)?; + let (i, chain_offset) = le_u32(i)?; + let (i, message_id) = le_u64(i)?; + let (i, _process_id) = le_u32(i)?; + let (i, tree_id) = le_u32(i)?; + let (i, session_id) = le_u64(i)?; + let (i, _signature) = take(16_usize)(i)?; + let (i, data) = if chain_offset > hlen as u32 { + take(chain_offset - hlen as u32)(i)? + } else { + rest(i)? + }; + let record = Smb2Record { + direction: flags.direction, + header_len: hlen, + nt_status: 0, + command, + message_id, + tree_id, + async_id: 0, + session_id, + data, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2NegotiateProtocolRequestRecord<'a> { + pub dialects_vec: Vec<u16>, + pub client_guid: &'a [u8], +} + +pub fn parse_smb2_request_negotiate_protocol( + i: &[u8], +) -> IResult<&[u8], Smb2NegotiateProtocolRequestRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, dialects_count) = le_u16(i)?; + let (i, _sec_mode) = le_u16(i)?; + let (i, _reserved1) = le_u16(i)?; + let (i, _capabilities) = le_u32(i)?; + let (i, client_guid) = take(16_usize)(i)?; + let (i, _ctx_offset) = le_u32(i)?; + let (i, _ctx_cnt) = le_u16(i)?; + let (i, _reserved2) = le_u16(i)?; + let (i, dia_vec) = count(le_u16, dialects_count as usize)(i)?; + let record = Smb2NegotiateProtocolRequestRecord { + dialects_vec: dia_vec, + client_guid, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2NegotiateProtocolResponseRecord<'a> { + pub dialect: u16, + pub server_guid: &'a [u8], + pub max_trans_size: u32, + pub max_read_size: u32, + pub max_write_size: u32, +} + +pub fn parse_smb2_response_negotiate_protocol( + i: &[u8], +) -> IResult<&[u8], Smb2NegotiateProtocolResponseRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, _skip1) = take(2_usize)(i)?; + let (i, dialect) = le_u16(i)?; + let (i, _ctx_cnt) = le_u16(i)?; + let (i, server_guid) = take(16_usize)(i)?; + let (i, _capabilities) = le_u32(i)?; + let (i, max_trans_size) = le_u32(i)?; + let (i, max_read_size) = le_u32(i)?; + let (i, max_write_size) = le_u32(i)?; + let record = Smb2NegotiateProtocolResponseRecord { + dialect, + server_guid, + max_trans_size, + max_read_size, + max_write_size, + }; + Ok((i, record)) +} + +pub fn parse_smb2_response_negotiate_protocol_error( + i: &[u8], +) -> IResult<&[u8], Smb2NegotiateProtocolResponseRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, _skip1) = take(2_usize)(i)?; + let record = Smb2NegotiateProtocolResponseRecord { + dialect: 0, + server_guid: &[], + max_trans_size: 0, + max_read_size: 0, + max_write_size: 0, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2SessionSetupRequestRecord<'a> { + pub data: &'a [u8], +} + +pub fn parse_smb2_request_session_setup(i: &[u8]) -> IResult<&[u8], Smb2SessionSetupRequestRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, _flags) = le_u8(i)?; + let (i, _security_mode) = le_u8(i)?; + let (i, _capabilities) = le_u32(i)?; + let (i, _channel) = le_u32(i)?; + let (i, _sec_offset) = le_u16(i)?; + let (i, _sec_len) = le_u16(i)?; + let (i, _prev_ssn_id) = take(8_usize)(i)?; + let (i, data) = rest(i)?; + let record = Smb2SessionSetupRequestRecord { data }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2TreeConnectRequestRecord<'a> { + pub share_name: &'a [u8], +} + +pub fn parse_smb2_request_tree_connect(i: &[u8]) -> IResult<&[u8], Smb2TreeConnectRequestRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, _offset_length) = take(4_usize)(i)?; + let (i, data) = rest(i)?; + let record = Smb2TreeConnectRequestRecord { share_name: data }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2TreeConnectResponseRecord { + pub share_type: u8, +} + +pub fn parse_smb2_response_tree_connect(i: &[u8]) -> IResult<&[u8], Smb2TreeConnectResponseRecord> { + let (i, _struct_size) = take(2_usize)(i)?; + let (i, share_type) = le_u8(i)?; + let (i, _share_flags) = le_u32(i)?; + let (i, _share_caps) = le_u32(i)?; + let (i, _access_mask) = le_u32(i)?; + let record = Smb2TreeConnectResponseRecord { share_type }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2CreateRequestRecord<'a> { + pub disposition: u32, + pub create_options: u32, + pub data: &'a [u8], +} + +pub fn parse_smb2_request_create(i: &[u8]) -> IResult<&[u8], Smb2CreateRequestRecord> { + let (i, _skip1) = take(36_usize)(i)?; + let (i, disposition) = le_u32(i)?; + let (i, create_options) = le_u32(i)?; + let (i, _file_name_offset) = le_u16(i)?; + let (i, file_name_length) = le_u16(i)?; + let (i, _skip2) = take(8_usize)(i)?; + let (i, data) = take(file_name_length)(i)?; + let (i, _skip3) = rest(i)?; + let record = Smb2CreateRequestRecord { + disposition, + create_options, + data, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2IOCtlRequestRecord<'a> { + pub is_pipe: bool, + pub function: u32, + pub guid: &'a [u8], + pub data: &'a [u8], +} + +pub fn parse_smb2_request_ioctl(i: &[u8]) -> IResult<&[u8], Smb2IOCtlRequestRecord> { + let (i, _skip) = take(2_usize)(i)?; // structure size + let (i, _) = take(2_usize)(i)?; // reserved + let (i, func) = le_u32(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, _indata_offset) = le_u32(i)?; + let (i, indata_len) = le_u32(i)?; + let (i, _) = take(4_usize)(i)?; + let (i, _outdata_offset) = le_u32(i)?; + let (i, _outdata_len) = le_u32(i)?; + let (i, _) = take(12_usize)(i)?; + let (i, data) = take(indata_len)(i)?; + let record = Smb2IOCtlRequestRecord { + is_pipe: (func == 0x0011c017), + function: func, + guid, + data, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2IOCtlResponseRecord<'a> { + pub is_pipe: bool, + pub guid: &'a [u8], + pub data: &'a [u8], + pub indata_len: u32, + pub outdata_len: u32, + pub indata_offset: u32, + pub outdata_offset: u32, +} + +pub fn parse_smb2_response_ioctl(i: &[u8]) -> IResult<&[u8], Smb2IOCtlResponseRecord> { + let (i, _skip) = take(2_usize)(i)?; // structure size + let (i, _) = take(2_usize)(i)?; // reserved + let (i, func) = le_u32(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, indata_offset) = le_u32(i)?; + let (i, indata_len) = le_u32(i)?; + let (i, outdata_offset) = le_u32(i)?; + let (i, outdata_len) = le_u32(i)?; + let (i, _) = take(8_usize)(i)?; + let (i, _) = take(indata_len)(i)?; + let (i, data) = take(outdata_len)(i)?; + let record = Smb2IOCtlResponseRecord { + is_pipe: (func == 0x0011c017), + guid, + data, + indata_len, + outdata_len, + indata_offset, + outdata_offset, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2CloseRequestRecord<'a> { + pub guid: &'a [u8], +} + +pub fn parse_smb2_request_close(i: &[u8]) -> IResult<&[u8], Smb2CloseRequestRecord> { + let (i, _skip) = take(8_usize)(i)?; + let (i, guid) = take(16_usize)(i)?; + let record = Smb2CloseRequestRecord { guid }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq)] +pub struct Smb2SetInfoRequestRenameRecord<'a> { + pub name: &'a [u8], +} + +pub fn parse_smb2_request_setinfo_rename(i: &[u8]) -> IResult<&[u8], Smb2SetInfoRequestData> { + let (i, _replace) = le_u8(i)?; + let (i, _reserved) = take(7_usize)(i)?; + let (i, _root_handle) = take(8_usize)(i)?; + let (i, name_len) = le_u32(i)?; + let (i, name) = take(name_len)(i)?; + let record = Smb2SetInfoRequestData::RENAME(Smb2SetInfoRequestRenameRecord { name }); + Ok((i, record)) +} + +#[derive(Debug, PartialEq)] +pub struct Smb2SetInfoRequestDispoRecord { + pub delete: bool, +} + +pub fn parse_smb2_request_setinfo_disposition(i: &[u8]) -> IResult<&[u8], Smb2SetInfoRequestData> { + let (i, info) = le_u8(i)?; + let record = Smb2SetInfoRequestData::DISPOSITION(Smb2SetInfoRequestDispoRecord { + delete: info & 1 != 0, + }); + Ok((i, record)) +} + +#[derive(Debug, PartialEq)] +pub enum Smb2SetInfoRequestData<'a> { + DISPOSITION(Smb2SetInfoRequestDispoRecord), + RENAME(Smb2SetInfoRequestRenameRecord<'a>), + UNHANDLED, +} + +#[derive(Debug)] +pub struct Smb2SetInfoRequestRecord<'a> { + pub guid: &'a [u8], + pub class: u8, + pub infolvl: u8, + pub data: Smb2SetInfoRequestData<'a>, +} + +fn parse_smb2_request_setinfo_data( + i: &[u8], class: u8, infolvl: u8, +) -> IResult<&[u8], Smb2SetInfoRequestData> { + if class == 1 { + // constants from [MS-FSCC] section 2.4 + match infolvl { + 10 => { + return parse_smb2_request_setinfo_rename(i); + } + 0xd => { + return parse_smb2_request_setinfo_disposition(i); + } + _ => {} + } + } + return Ok((i, Smb2SetInfoRequestData::UNHANDLED)); +} + +pub fn parse_smb2_request_setinfo(i: &[u8]) -> IResult<&[u8], Smb2SetInfoRequestRecord> { + let (i, _struct_size) = le_u16(i)?; + let (i, class) = le_u8(i)?; + let (i, infolvl) = le_u8(i)?; + let (i, setinfo_size) = le_u32(i)?; + let (i, _setinfo_offset) = le_u16(i)?; + let (i, _reserved) = take(2_usize)(i)?; + let (i, _additional_info) = le_u32(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, data) = map_parser(take(setinfo_size), |b| { + parse_smb2_request_setinfo_data(b, class, infolvl) + })(i)?; + let record = Smb2SetInfoRequestRecord { + guid, + class, + infolvl, + data, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2WriteRequestRecord<'a> { + pub wr_len: u32, + pub wr_offset: u64, + pub guid: &'a [u8], + pub data: &'a [u8], +} + +// can be called on incomplete records +pub fn parse_smb2_request_write(i: &[u8]) -> IResult<&[u8], Smb2WriteRequestRecord> { + let (i, _skip1) = take(4_usize)(i)?; + let (i, wr_len) = le_u32(i)?; + let (i, wr_offset) = le_u64(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, _channel) = le_u32(i)?; + let (i, _remaining_bytes) = le_u32(i)?; + let (i, _write_flags) = le_u32(i)?; + let (i, _skip2) = take(4_usize)(i)?; + let (i, data) = parse_smb2_data(i, wr_len)?; + let record = Smb2WriteRequestRecord { + wr_len, + wr_offset, + guid, + data, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2ReadRequestRecord<'a> { + pub rd_len: u32, + pub rd_offset: u64, + pub guid: &'a [u8], +} + +pub fn parse_smb2_request_read(i: &[u8]) -> IResult<&[u8], Smb2ReadRequestRecord> { + let (i, _skip1) = take(4_usize)(i)?; + let (i, rd_len) = le_u32(i)?; + let (i, rd_offset) = le_u64(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, _min_count) = le_u32(i)?; + let (i, _channel) = le_u32(i)?; + let (i, _remaining_bytes) = le_u32(i)?; + let (i, _skip2) = take(4_usize)(i)?; + let record = Smb2ReadRequestRecord { + rd_len, + rd_offset, + guid, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2ReadResponseRecord<'a> { + pub len: u32, + pub data: &'a [u8], +} + +// parse read/write data. If all is available, 'take' it. +// otherwise just return what we have. So this may return +// partial data. +fn parse_smb2_data(i: &[u8], len: u32) -> IResult<&[u8], &[u8]> { + if len as usize > i.len() { + rest(i) + } else { + take(len)(i) + } +} + +// can be called on incomplete records +pub fn parse_smb2_response_read(i: &[u8]) -> IResult<&[u8], Smb2ReadResponseRecord> { + let (i, _struct_size) = le_u16(i)?; + let (i, _data_offset) = le_u16(i)?; + let (i, rd_len) = le_u32(i)?; + let (i, _rd_rem) = le_u32(i)?; + let (i, _padding) = take(4_usize)(i)?; + let (i, data) = parse_smb2_data(i, rd_len)?; + let record = Smb2ReadResponseRecord { len: rd_len, data }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2CreateResponseRecord<'a> { + pub guid: &'a [u8], + pub create_ts: SMBFiletime, + pub last_access_ts: SMBFiletime, + pub last_write_ts: SMBFiletime, + pub last_change_ts: SMBFiletime, + pub size: u64, +} + +pub fn parse_smb2_response_create(i: &[u8]) -> IResult<&[u8], Smb2CreateResponseRecord> { + let (i, _ssize) = le_u16(i)?; + let (i, _oplock) = le_u8(i)?; + let (i, _resp_flags) = le_u8(i)?; + let (i, _create_action) = le_u32(i)?; + let (i, create_ts) = le_u64(i)?; + let (i, last_access_ts) = le_u64(i)?; + let (i, last_write_ts) = le_u64(i)?; + let (i, last_change_ts) = le_u64(i)?; + let (i, _alloc_size) = le_u64(i)?; + let (i, eof) = le_u64(i)?; + let (i, _attrs) = le_u32(i)?; + let (i, _padding) = take(4_usize)(i)?; + let (i, guid) = take(16_usize)(i)?; + let (i, _skip2) = take(8_usize)(i)?; + let record = Smb2CreateResponseRecord { + guid, + create_ts: SMBFiletime::new(create_ts), + last_access_ts: SMBFiletime::new(last_access_ts), + last_write_ts: SMBFiletime::new(last_write_ts), + last_change_ts: SMBFiletime::new(last_change_ts), + size: eof, + }; + Ok((i, record)) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Smb2WriteResponseRecord { + pub wr_cnt: u32, +} + +pub fn parse_smb2_response_write(i: &[u8]) -> IResult<&[u8], Smb2WriteResponseRecord> { + let (i, _skip1) = take(4_usize)(i)?; + let (i, wr_cnt) = le_u32(i)?; + let (i, _skip2) = take(6_usize)(i)?; + let record = Smb2WriteResponseRecord { wr_cnt }; + Ok((i, record)) +} + +pub fn parse_smb2_response_record(i: &[u8]) -> IResult<&[u8], Smb2Record> { + let (i, _) = tag(b"\xfeSMB")(i)?; + let (i, hlen) = le_u16(i)?; + let (i, _credit_charge) = le_u16(i)?; + let (i, nt_status) = le_u32(i)?; + let (i, command) = le_u16(i)?; + let (i, _credit_granted) = le_u16(i)?; + let (i, flags) = parse_smb2_flags(i)?; + let (i, chain_offset) = le_u32(i)?; + let (i, message_id) = le_u64(i)?; + let (i, _process_id) = cond(flags.async_command == 0, le_u32)(i)?; + let (i, tree_id) = cond(flags.async_command == 0, le_u32)(i)?; + let (i, async_id) = cond(flags.async_command == 1, le_u64)(i)?; + let (i, session_id) = le_u64(i)?; + let (i, _signature) = take(16_usize)(i)?; + let (i, data) = if chain_offset > hlen as u32 { + take(chain_offset - hlen as u32)(i)? + } else { + rest(i)? + }; + let record = Smb2Record { + direction: flags.direction, + header_len: hlen, + nt_status, + message_id, + tree_id: tree_id.unwrap_or(0), + async_id: async_id.unwrap_or(0), + session_id, + command, + data, + }; + Ok((i, record)) +} + +fn smb_basic_search(d: &[u8]) -> usize { + let needle = b"SMB"; + // this could be replaced by aho-corasick + let iter = d.windows(needle.len()); + for (r, window) in iter.enumerate() { + if window == needle { + return r; + } + } + return 0; +} + +pub fn search_smb_record(i: &[u8]) -> IResult<&[u8], &[u8]> { + let mut d = i; + while d.len() >= 4 { + let index = smb_basic_search(d); + if index == 0 { + return Err(Err::Error(make_error(d, ErrorKind::Eof))); + } + if d[index - 1] == 0xfe || d[index - 1] == 0xff || d[index - 1] == 0xfd { + // if we have enough data, check nbss + if index < 5 || d[index - 5] == NBSS_MSGTYPE_SESSION_MESSAGE { + return Ok((&d[index + 3..], &d[index - 1..])); + } + } + d = &d[index + 3..]; + } + Err(Err::Incomplete(Needed::new(4_usize - d.len()))) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::smb::smb2::smb2_dialect_string; + use std::convert::TryInto; + fn guid_to_string(guid: &[u8]) -> String { + if guid.len() == 16 { + let output = format!("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}", + guid[3], guid[2], guid[1], guid[0], + guid[5], guid[4], guid[7], guid[6], + guid[9], guid[8], guid[11], guid[10], + guid[15], guid[14], guid[13], guid[12]); + output + } else { + "".to_string() + } + } + #[test] + fn test_parse_smb2_request_record() { + let data = hex::decode("fe534d42400000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let result = parse_smb2_request_record(&data).unwrap(); + let record: Smb2Record = result.1; + assert_eq!( + record, + Smb2Record { + direction: 1, + header_len: 64, + nt_status: 0, + command: 0, + message_id: 0, + tree_id: 0, + async_id: 0, + session_id: 0, + data: &[], + } + ); + } + #[test] + fn test_parse_smb2_request_negotiate_protocol() { + // https://github.com/bro/bro/blob/master/testing/btest/Traces/smb/smb3_negotiate_context.pcap + // smb3_negotiate_context.pcap no.12 + let data = hex::decode("24000800010000007f00000016ab4fd9625676488cd1707d08e52b5878000000020000000202100222022402000302031003110300000000010026000000000001002000010067e5f669ff3e0ad12e89ad84ceb1d35dfee53ede3e4858a6d1a9099ac1635a9600000200060000000000020001000200").unwrap(); + let result = parse_smb2_request_negotiate_protocol(&data).unwrap(); + let record: Smb2NegotiateProtocolRequestRecord = result.1; + let dialects: Vec<String> = record + .dialects_vec + .iter() + .map(|d| smb2_dialect_string(*d)) + .collect(); + assert_eq!( + dialects, + ["2.02", "2.10", "2.22", "2.24", "3.00", "3.02", "3.10", "3.11"] + ); + assert_eq!( + guid_to_string(record.client_guid), + "d94fab16-5662-4876-d18c-7d70582be508" + ); // TODO: guid order + } + + #[test] + fn test_parse_smb2_response_tree_connect() { + // https://github.com/bro/bro/blob/master/testing/btest/Traces/smb/smb2.pcap + // filter:smb2 no.11 + let data = hex::decode("100001000008000000000000ff011f00").unwrap(); + let result = parse_smb2_response_tree_connect(&data).unwrap(); + let record: Smb2TreeConnectResponseRecord = result.1; + assert_eq!(record.share_type, 1); // 1: SMB2_SHARE_TYPE_DISK + } + #[test] + fn test_parse_smb2_request_create() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:smb2 no.26 + let data = hex::decode("390000000200000000000000000000000000000000000000810010008000000003000000020000002100200078000000800000005800000000007200760073002800000010000400000018001000000044486e510000000000000000000000000000000000000000180000001000040000001800000000004d78416300000000000000001000040000001800000000005146696400000000").unwrap(); + let result = parse_smb2_request_create(&data).unwrap(); + let record: Smb2CreateRequestRecord = result.1; + assert_eq!(record.disposition, 2); // FILE_CREATE: 2 + assert_eq!(record.create_options, 0x200021); + assert_eq!(record.data, &[]); + let del = record.create_options & 0x0000_1000 != 0; + let dir = record.create_options & 0x0000_0001 != 0; + assert!(!del); + assert!(dir); + } + #[test] + fn test_parse_smb2_request_close() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:smb2 no.24 + let data = hex::decode("1800000000000000490000000000000005000000ffffffff").unwrap(); + let result = parse_smb2_request_close(&data).unwrap(); + let record: Smb2CloseRequestRecord = result.1; + assert_eq!( + guid_to_string(record.guid), + "00000049-0000-0000-0005-0000ffffffff" + ); + } + + #[test] + fn test_parse_smb2_request_setinfo() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:tcp.stream eq 0 no.36 + let data = hex::decode( + "210001140800000060000000000000004d0000000000000009000000ffffffff4b06170000000000", + ) + .unwrap(); + let result = parse_smb2_request_setinfo(&data).unwrap(); + let record: Smb2SetInfoRequestRecord = result.1; + assert_eq!(record.class, 1); + assert_eq!(record.infolvl, 20); + assert_eq!(record.data, Smb2SetInfoRequestData::UNHANDLED); + assert_eq!( + guid_to_string(record.guid), + "0000004d-0000-0000-0009-0000ffffffff" + ); + } + + #[test] + fn test_parse_smb2_request_read() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:smb2 no.20 + let data = hex::decode("31005000000400000000000000000000490000000000000005000000ffffffff00000000000000000000000000000000").unwrap(); + let result = parse_smb2_request_read(&data).unwrap(); + let record: Smb2ReadRequestRecord = result.1; + assert_eq!(record.rd_len, 1024); + assert_eq!(record.rd_offset, 0); + assert_eq!( + guid_to_string(record.guid), + "00000049-0000-0000-0005-0000ffffffff" + ); + } + + #[test] + fn test_parse_smb2_request_write() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:tcp.stream eq 0 no.18 + let data = hex::decode("31007000740000000000000000000000490000000000000005000000ffffffff0000000000000000000000000000000005000b03100000007400000001000000b810b810000000000200000000000100c84f324b7016d30112785a47bf6ee18803000000045d888aeb1cc9119fe808002b1048600200000001000100c84f324b7016d30112785a47bf6ee188030000002c1cb76c12984045030000000000000001000000").unwrap(); + let result = parse_smb2_request_write(&data).unwrap(); + let record: Smb2WriteRequestRecord = result.1; + assert_eq!(record.wr_len, 116); + assert_eq!(record.wr_offset, 0); + assert_eq!( + guid_to_string(record.guid), + "00000049-0000-0000-0005-0000ffffffff" + ); + assert_eq!(record.data.len(), 116); + } + + #[test] + fn test_parse_smb2_response_read() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb2.pcap + // filter:tcp.stream eq 0 no.21 + let data = hex::decode("110050005c000000000000000000000005000c03100000005c00000001000000b810b810b97200000d005c504950455c73727673766300000200000000000000045d888aeb1cc9119fe808002b10486002000000030003000000000000000000000000000000000000000000").unwrap(); + let result = parse_smb2_response_read(&data).unwrap(); + let record: Smb2ReadResponseRecord = result.1; + assert_eq!(record.len, 92); + assert_eq!(record.data.len(), 92); + } + #[test] + fn test_parse_smb2_record_direction() { + let data = hex::decode("fe534d42400000000000000000000100010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let result = parse_smb2_record_direction(&data).unwrap(); + let record: Smb2RecordDir = result.1; + assert!(!record.request); + let data = hex::decode("fe534d4240000000000000000100080000000000000000000100000000000000fffe000000000000000000000000000000000000000000000000000000000000").unwrap(); + let result = parse_smb2_record_direction(&data).unwrap(); + let record: Smb2RecordDir = result.1; + assert!(record.request); + } + + #[test] + fn test_parse_smb2_request_tree_connect() { + let data = hex::decode("0900000048002c005c005c003100390032002e003100360038002e003100390039002e003100330033005c004900500043002400").unwrap(); + let result = parse_smb2_request_tree_connect(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert!(record.share_name.len() > 2); + let share_name_len = u16::from_le_bytes(record.share_name[0..2].try_into().unwrap()); + assert_eq!(share_name_len, 44); + assert_eq!(record.share_name.len(), share_name_len as usize + 2); + let mut share_name = record.share_name[2..].to_vec(); + share_name.retain(|&i| i != 0x00); + assert_eq!( + String::from_utf8_lossy(&share_name), + "\\\\192.168.199.133\\IPC$" + ); + } + + #[test] + fn test_parse_smb2_response_record() { + let data = hex::decode("fe534d4240000000000000000000010001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041000100ff020000966eafa3357f0440a5f9643e1bfa8c56070000000000800000008000000080001064882d8527d201a1f3ae878427d20180004001000000006082013c06062b0601050502a08201303082012ca01a3018060a2b06010401823702021e060a2b06010401823702020aa282010c048201084e45474f45585453010000000000000060000000700000007fb23ba7cacc4e216323ca8472061efbd2c4f6d6b3017012f0bf4f7202ec684ee801ef64e55401ab86b1c9ebde4e39ea0000000000000000600000000100000000000000000000005c33530deaf90d4db2ec4ae3786ec3084e45474f45585453030000000100000040000000980000007fb23ba7cacc4e216323ca8472061efb5c33530deaf90d4db2ec4ae3786ec30840000000580000003056a05430523027802530233121301f06035504031318546f6b656e205369676e696e67205075626c6963204b65793027802530233121301f06035504031318546f6b656e205369676e696e67205075626c6963204b6579").unwrap(); + let result = parse_smb2_response_record(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!(record.direction, 1); + assert_eq!(record.header_len, 64); + assert_eq!(record.nt_status, 0); + assert_eq!( + record.command, + crate::smb::smb2::SMB2_COMMAND_NEGOTIATE_PROTOCOL + ); + assert_eq!(record.message_id, 0); + assert_eq!(record.tree_id, 0); + assert_eq!(record.async_id, 0); + assert_eq!(record.session_id, 0); + let neg_proto_result = parse_smb2_response_negotiate_protocol(record.data); + assert!(neg_proto_result.is_ok()); + let neg_proto = neg_proto_result.unwrap().1; + assert_eq!( + guid_to_string(neg_proto.server_guid), + "a3af6e96-7f35-4004-f9a5-3e64568cfa1b" + ); + assert_eq!(neg_proto.dialect, 0x2ff); + assert_eq!(smb2_dialect_string(neg_proto.dialect), "2.??".to_string()); + assert_eq!(neg_proto.max_trans_size, 0x800000); + assert_eq!(neg_proto.max_read_size, 0x800000); + assert_eq!(neg_proto.max_write_size, 0x800000); + } + + #[test] + fn test_todo_parse_smb2_response_negotiate_protocol_error() { + // TODO: find pcap + } + + #[test] + fn test_parse_smb2_response_write() { + let data = hex::decode("11000000a00000000000000000000000").unwrap(); + let result = parse_smb2_response_write(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!(record.wr_cnt, 160); + } + #[test] + fn test_parse_smb2_response_create() { + let data = hex::decode("5900000001000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000800000000000000001000000db3b5a009a29ea00000000000000000000000000").unwrap(); + let result = parse_smb2_response_create(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!( + guid_to_string(record.guid), + "00000001-3bdb-005a-299a-00ea00000000" + ); + assert_eq!(record.create_ts, SMBFiletime::new(0)); + assert_eq!(record.last_access_ts, SMBFiletime::new(0)); + assert_eq!(record.last_write_ts, SMBFiletime::new(0)); + assert_eq!(record.last_change_ts, SMBFiletime::new(0)); + assert_eq!(record.size, 0); + } + #[test] + fn test_parse_smb2_response_ioctl() { + let data = hex::decode("31000000fc011400ffffffffffffffffffffffffffffffff7000000000000000700000003001000000000000000000009800000004000000010000000000000000ca9a3b0000000002000000c0a8c7850000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000010000000000000000ca9a3b000000001700000000000000fe8000000000000065b53a9792d191990000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(); + let result = parse_smb2_response_ioctl(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!(record.indata_len, 0); + assert_eq!( + guid_to_string(record.guid), + "ffffffff-ffff-ffff-ffff-ffffffffffff" + ); + assert!(!record.is_pipe); + assert_eq!(record.outdata_len, 304); + assert_eq!(record.indata_offset, 112); + assert_eq!(record.outdata_offset, 112); + } + + #[test] + fn test_parse_smb2_request_ioctl() { + let data = hex::decode("39000000fc011400ffffffffffffffffffffffffffffffff7800000000000000000000007800000000000000000001000100000000000000").unwrap(); + let result = parse_smb2_request_ioctl(&data); + assert!(result.is_ok()); + let record = result.unwrap().1; + assert_eq!( + guid_to_string(record.guid), + "ffffffff-ffff-ffff-ffff-ffffffffffff" + ); + assert!(!record.is_pipe); + assert_eq!(record.function, 0x1401fc); + assert_eq!(record.data, &[]); + } +} diff --git a/rust/src/smb/smb2_session.rs b/rust/src/smb/smb2_session.rs new file mode 100644 index 0000000..93cc99c --- /dev/null +++ b/rust/src/smb/smb2_session.rs @@ -0,0 +1,85 @@ +/* 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. + */ + +use crate::smb::smb2_records::*; +use crate::smb::smb::*; +use crate::smb::events::*; +use crate::smb::auth::*; + +pub fn smb2_session_setup_request(state: &mut SMBState, r: &Smb2Record) +{ + SCLogDebug!("SMB2_COMMAND_SESSION_SETUP: r.data.len() {}", r.data.len()); + #[allow(clippy::single_match)] + match parse_smb2_request_session_setup(r.data) { + Ok((_, setup)) => { + let hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); + let tx = state.new_sessionsetup_tx(hdr); + tx.vercmd.set_smb2_cmd(r.command); + + if let Some(SMBTransactionTypeData::SESSIONSETUP(ref mut td)) = tx.type_data { + if let Some(s) = parse_secblob(setup.data) { + td.ntlmssp = s.ntlmssp; + td.krb_ticket = s.krb; + if let Some(ntlm) = &td.ntlmssp { + if ntlm.warning { + tx.set_event(SMBEvent::UnusualNtlmsspOrder); + } + } + } + } + }, + _ => { +// events.push(SMBEvent::MalformedData); + }, + } +} + +fn smb2_session_setup_update_tx(tx: &mut SMBTransaction, r: &Smb2Record) +{ + tx.hdr = SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER); // to overwrite ssn_id 0 + tx.set_status(r.nt_status, false); + tx.response_done = true; +} + +pub fn smb2_session_setup_response(state: &mut SMBState, r: &Smb2Record) +{ + // try exact match with session id already set (e.g. NTLMSSP AUTH phase) + let found = r.session_id != 0 && match state.get_sessionsetup_tx( + SMBCommonHdr::from2(r, SMBHDR_TYPE_HEADER)) + { + Some(tx) => { + smb2_session_setup_update_tx(tx, r); + SCLogDebug!("smb2_session_setup_response: tx {:?}", tx); + true + }, + None => { false }, + }; + // otherwise try match with ssn id 0 (e.g. NTLMSSP_NEGOTIATE) + if !found { + match state.get_sessionsetup_tx( + SMBCommonHdr::new(SMBHDR_TYPE_HEADER, 0, 0, r.message_id)) + { + Some(tx) => { + smb2_session_setup_update_tx(tx, r); + SCLogDebug!("smb2_session_setup_response: tx {:?}", tx); + }, + None => { + SCLogDebug!("smb2_session_setup_response: tx not found for {:?}", r); + }, + } + } +} diff --git a/rust/src/smb/smb3.rs b/rust/src/smb/smb3.rs new file mode 100644 index 0000000..07a7059 --- /dev/null +++ b/rust/src/smb/smb3.rs @@ -0,0 +1,59 @@ +/* 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. + */ + +use nom7::bytes::streaming::{tag, take}; +use nom7::number::streaming::{le_u16, le_u32, le_u64}; +use nom7::IResult; + +#[derive(Debug,PartialEq, Eq)] +pub struct Smb3TransformRecord<'a> { + pub session_id: u64, + pub enc_algo: u16, + pub enc_data: &'a[u8], +} + +pub fn parse_smb3_transform_record(i: &[u8]) -> IResult<&[u8], Smb3TransformRecord> { + let (i, _) = tag(b"\xfdSMB")(i)?; + let (i, _signature) = take(16_usize)(i)?; + let (i, _nonce) = take(16_usize)(i)?; + let (i, msg_size) = le_u32(i)?; + let (i, _reserved) = le_u16(i)?; + let (i, enc_algo) = le_u16(i)?; + let (i, session_id) = le_u64(i)?; + let (i, enc_data) = take(msg_size)(i)?; + let record = Smb3TransformRecord { + session_id, + enc_algo, + enc_data, + }; + Ok((i, record)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_parse_smb3_transform_record() { + // https://raw.githubusercontent.com/bro/bro/master/testing/btest/Traces/smb/smb3.pcap + let data = hex::decode("fd534d42188d39cea4b1e3f640aff5d0b1569852c0bd665516dbb4b499507f000000000069000000000001003d00009400480000d9f8a66572b40c621bea6f5922a412a8eb2e3cc2af9ce26a277e75898cb523b9eb49ef660a6a1a09368fadd6a58e893e08eb3b7c068bdb74b6cd38e9ed1a2559cefb2ebc2172fd86c08a1a636eb851f20bf53a242f4cfaf7ab44e77291073ad492d6297c3d3a67757c").unwrap(); + let result = parse_smb3_transform_record(&data).unwrap(); + let record: Smb3TransformRecord = result.1; + assert_eq!(record.session_id, 79167320227901); + assert_eq!(record.enc_algo, 1); + assert_eq!(record.enc_data.len(), 105); + } +} diff --git a/rust/src/smb/smb_records.rs b/rust/src/smb/smb_records.rs new file mode 100644 index 0000000..cc5b3cb --- /dev/null +++ b/rust/src/smb/smb_records.rs @@ -0,0 +1,53 @@ +/* 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. + */ + +use crate::common::nom7::take_until_and_consume; +use crate::smb::error::SmbError; +use nom7::{Err, IResult}; + +/// parse a UTF16 string that is null terminated. Normally by 2 null +/// bytes, but at the end of the data it can also be a single null. +/// Skip every second byte. +pub fn smb_get_unicode_string(blob: &[u8]) -> IResult<&[u8], Vec<u8>, SmbError> +{ + SCLogDebug!("get_unicode_string: blob {} {:?}", blob.len(), blob); + let mut name : Vec<u8> = Vec::new(); + let mut c = blob; + while !c.is_empty() { + if c.len() == 1 && c[0] == 0 { + let rem = &c[1..]; + SCLogDebug!("get_unicode_string: name {:?}", name); + return Ok((rem, name)) + } else if c.len() == 1 { + break; + } else if c[0] == 0 && c[1] == 0 { + let rem = &c[2..]; + SCLogDebug!("get_unicode_string: name {:?}", name); + return Ok((rem, name)) + } + name.push(c[0]); + c = &c[2..]; + } + Err(Err::Error(SmbError::BadEncoding)) +} + +// parse an ASCII string that is null terminated +pub fn smb_get_ascii_string(i: &[u8]) -> IResult<&[u8], Vec<u8>, SmbError> { + let (i, s) = take_until_and_consume(b"\x00")(i)?; + Ok((i, s.to_vec())) +} + diff --git a/rust/src/smb/smb_status.rs b/rust/src/smb/smb_status.rs new file mode 100644 index 0000000..10b06a5 --- /dev/null +++ b/rust/src/smb/smb_status.rs @@ -0,0 +1,3609 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +pub const SMB_NTSTATUS_SUCCESS: u32 = 0x00000000; +pub const SMB_NTSTATUS_WAIT_1: u32 = 0x00000001; +pub const SMB_NTSTATUS_WAIT_2: u32 = 0x00000002; +pub const SMB_NTSTATUS_WAIT_3: u32 = 0x00000003; +pub const SMB_NTSTATUS_WAIT_63: u32 = 0x0000003f; +pub const SMB_NTSTATUS_ABANDONED: u32 = 0x00000080; +pub const SMB_NTSTATUS_ABANDONED_WAIT_63: u32 = 0x000000bf; +pub const SMB_NTSTATUS_USER_APC: u32 = 0x000000c0; +pub const SMB_NTSTATUS_ALERTED: u32 = 0x00000101; +pub const SMB_NTSTATUS_TIMEOUT: u32 = 0x00000102; +pub const SMB_NTSTATUS_PENDING: u32 = 0x00000103; +pub const SMB_NTSTATUS_REPARSE: u32 = 0x00000104; +pub const SMB_NTSTATUS_MORE_ENTRIES: u32 = 0x00000105; +pub const SMB_NTSTATUS_NOT_ALL_ASSIGNED: u32 = 0x00000106; +pub const SMB_NTSTATUS_SOME_NOT_MAPPED: u32 = 0x00000107; +pub const SMB_NTSTATUS_OPLOCK_BREAK_IN_PROGRESS: u32 = 0x00000108; +pub const SMB_NTSTATUS_VOLUME_MOUNTED: u32 = 0x00000109; +pub const SMB_NTSTATUS_RXACT_COMMITTED: u32 = 0x0000010a; +pub const SMB_NTSTATUS_NOTIFY_CLEANUP: u32 = 0x0000010b; +pub const SMB_NTSTATUS_NOTIFY_ENUM_DIR: u32 = 0x0000010c; +pub const SMB_NTSTATUS_NO_QUOTAS_FOR_ACCOUNT: u32 = 0x0000010d; +pub const SMB_NTSTATUS_PRIMARY_TRANSPORT_CONNECT_FAILED: u32 = 0x0000010e; +pub const SMB_NTSTATUS_PAGE_FAULT_TRANSITION: u32 = 0x00000110; +pub const SMB_NTSTATUS_PAGE_FAULT_DEMAND_ZERO: u32 = 0x00000111; +pub const SMB_NTSTATUS_PAGE_FAULT_COPY_ON_WRITE: u32 = 0x00000112; +pub const SMB_NTSTATUS_PAGE_FAULT_GUARD_PAGE: u32 = 0x00000113; +pub const SMB_NTSTATUS_PAGE_FAULT_PAGING_FILE: u32 = 0x00000114; +pub const SMB_NTSTATUS_CACHE_PAGE_LOCKED: u32 = 0x00000115; +pub const SMB_NTSTATUS_CRASH_DUMP: u32 = 0x00000116; +pub const SMB_NTSTATUS_BUFFER_ALL_ZEROS: u32 = 0x00000117; +pub const SMB_NTSTATUS_REPARSE_OBJECT: u32 = 0x00000118; +pub const SMB_NTSTATUS_RESOURCE_REQUIREMENTS_CHANGED: u32 = 0x00000119; +pub const SMB_NTSTATUS_TRANSLATION_COMPLETE: u32 = 0x00000120; +pub const SMB_NTSTATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY: u32 = 0x00000121; +pub const SMB_NTSTATUS_NOTHING_TO_TERMINATE: u32 = 0x00000122; +pub const SMB_NTSTATUS_PROCESS_NOT_IN_JOB: u32 = 0x00000123; +pub const SMB_NTSTATUS_PROCESS_IN_JOB: u32 = 0x00000124; +pub const SMB_NTSTATUS_VOLSNAP_HIBERNATE_READY: u32 = 0x00000125; +pub const SMB_NTSTATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY: u32 = 0x00000126; +pub const SMB_NTSTATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED: u32 = 0x00000127; +pub const SMB_NTSTATUS_INTERRUPT_STILL_CONNECTED: u32 = 0x00000128; +pub const SMB_NTSTATUS_PROCESS_CLONED: u32 = 0x00000129; +pub const SMB_NTSTATUS_FILE_LOCKED_WITH_ONLY_READERS: u32 = 0x0000012a; +pub const SMB_NTSTATUS_FILE_LOCKED_WITH_WRITERS: u32 = 0x0000012b; +pub const SMB_NTSTATUS_RESOURCEMANAGER_READ_ONLY: u32 = 0x00000202; +pub const SMB_NTSTATUS_WAIT_FOR_OPLOCK: u32 = 0x00000367; +pub const SMB_NTDBG_EXCEPTION_HANDLED: u32 = 0x00010001; +pub const SMB_NTDBG_CONTINUE: u32 = 0x00010002; +pub const SMB_NTSTATUS_FLT_IO_COMPLETE: u32 = 0x001c0001; +pub const SMB_NTSTATUS_FILE_NOT_AVAILABLE: u32 = 0xc0000467; +pub const SMB_NTSTATUS_SHARE_UNAVAILABLE: u32 = 0xc0000480; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_THREAD_AFFINITY: u32 = 0xc0000721; +pub const SMB_NTSTATUS_OBJECT_NAME_EXISTS: u32 = 0x40000000; +pub const SMB_NTSTATUS_THREAD_WAS_SUSPENDED: u32 = 0x40000001; +pub const SMB_NTSTATUS_WORKING_SET_LIMIT_RANGE: u32 = 0x40000002; +pub const SMB_NTSTATUS_IMAGE_NOT_AT_BASE: u32 = 0x40000003; +pub const SMB_NTSTATUS_RXACT_STATE_CREATED: u32 = 0x40000004; +pub const SMB_NTSTATUS_SEGMENT_NOTIFICATION: u32 = 0x40000005; +pub const SMB_NTSTATUS_LOCAL_USER_SESSION_KEY: u32 = 0x40000006; +pub const SMB_NTSTATUS_BAD_CURRENT_DIRECTORY: u32 = 0x40000007; +pub const SMB_NTSTATUS_SERIAL_MORE_WRITES: u32 = 0x40000008; +pub const SMB_NTSTATUS_REGISTRY_RECOVERED: u32 = 0x40000009; +pub const SMB_NTSTATUS_FT_READ_RECOVERY_FROM_BACKUP: u32 = 0x4000000a; +pub const SMB_NTSTATUS_FT_WRITE_RECOVERY: u32 = 0x4000000b; +pub const SMB_NTSTATUS_SERIAL_COUNTER_TIMEOUT: u32 = 0x4000000c; +pub const SMB_NTSTATUS_NULL_LM_PASSWORD: u32 = 0x4000000d; +pub const SMB_NTSTATUS_IMAGE_MACHINE_TYPE_MISMATCH: u32 = 0x4000000e; +pub const SMB_NTSTATUS_RECEIVE_PARTIAL: u32 = 0x4000000f; +pub const SMB_NTSTATUS_RECEIVE_EXPEDITED: u32 = 0x40000010; +pub const SMB_NTSTATUS_RECEIVE_PARTIAL_EXPEDITED: u32 = 0x40000011; +pub const SMB_NTSTATUS_EVENT_DONE: u32 = 0x40000012; +pub const SMB_NTSTATUS_EVENT_PENDING: u32 = 0x40000013; +pub const SMB_NTSTATUS_CHECKING_FILE_SYSTEM: u32 = 0x40000014; +pub const SMB_NTSTATUS_FATAL_APP_EXIT: u32 = 0x40000015; +pub const SMB_NTSTATUS_PREDEFINED_HANDLE: u32 = 0x40000016; +pub const SMB_NTSTATUS_WAS_UNLOCKED: u32 = 0x40000017; +pub const SMB_NTSTATUS_SERVICE_NOTIFICATION: u32 = 0x40000018; +pub const SMB_NTSTATUS_WAS_LOCKED: u32 = 0x40000019; +pub const SMB_NTSTATUS_LOG_HARD_ERROR: u32 = 0x4000001a; +pub const SMB_NTSTATUS_ALREADY_WIN32: u32 = 0x4000001b; +pub const SMB_NTSTATUS_WX86_UNSIMULATE: u32 = 0x4000001c; +pub const SMB_NTSTATUS_WX86_CONTINUE: u32 = 0x4000001d; +pub const SMB_NTSTATUS_WX86_SINGLE_STEP: u32 = 0x4000001e; +pub const SMB_NTSTATUS_WX86_BREAKPOINT: u32 = 0x4000001f; +pub const SMB_NTSTATUS_WX86_EXCEPTION_CONTINUE: u32 = 0x40000020; +pub const SMB_NTSTATUS_WX86_EXCEPTION_LASTCHANCE: u32 = 0x40000021; +pub const SMB_NTSTATUS_WX86_EXCEPTION_CHAIN: u32 = 0x40000022; +pub const SMB_NTSTATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE: u32 = 0x40000023; +pub const SMB_NTSTATUS_NO_YIELD_PERFORMED: u32 = 0x40000024; +pub const SMB_NTSTATUS_TIMER_RESUME_IGNORED: u32 = 0x40000025; +pub const SMB_NTSTATUS_ARBITRATION_UNHANDLED: u32 = 0x40000026; +pub const SMB_NTSTATUS_CARDBUS_NOT_SUPPORTED: u32 = 0x40000027; +pub const SMB_NTSTATUS_WX86_CREATEWX86TIB: u32 = 0x40000028; +pub const SMB_NTSTATUS_MP_PROCESSOR_MISMATCH: u32 = 0x40000029; +pub const SMB_NTSTATUS_HIBERNATED: u32 = 0x4000002a; +pub const SMB_NTSTATUS_RESUME_HIBERNATION: u32 = 0x4000002b; +pub const SMB_NTSTATUS_FIRMWARE_UPDATED: u32 = 0x4000002c; +pub const SMB_NTSTATUS_DRIVERS_LEAKING_LOCKED_PAGES: u32 = 0x4000002d; +pub const SMB_NTSTATUS_MESSAGE_RETRIEVED: u32 = 0x4000002e; +pub const SMB_NTSTATUS_SYSTEM_POWERSTATE_TRANSITION: u32 = 0x4000002f; +pub const SMB_NTSTATUS_ALPC_CHECK_COMPLETION_LIST: u32 = 0x40000030; +pub const SMB_NTSTATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION: u32 = 0x40000031; +pub const SMB_NTSTATUS_ACCESS_AUDIT_BY_POLICY: u32 = 0x40000032; +pub const SMB_NTSTATUS_ABANDON_HIBERFILE: u32 = 0x40000033; +pub const SMB_NTSTATUS_BIZRULES_NOT_ENABLED: u32 = 0x40000034; +pub const SMB_NTSTATUS_WAKE_SYSTEM: u32 = 0x40000294; +pub const SMB_NTSTATUS_DS_SHUTTING_DOWN: u32 = 0x40000370; +pub const SMB_NTDBG_REPLY_LATER: u32 = 0x40010001; +pub const SMB_NTDBG_UNABLE_TO_PROVIDE_HANDLE: u32 = 0x40010002; +pub const SMB_NTDBG_TERMINATE_THREAD: u32 = 0x40010003; +pub const SMB_NTDBG_TERMINATE_PROCESS: u32 = 0x40010004; +pub const SMB_NTDBG_CONTROL_C: u32 = 0x40010005; +pub const SMB_NTDBG_PRINTEXCEPTION_C: u32 = 0x40010006; +pub const SMB_NTDBG_RIPEXCEPTION: u32 = 0x40010007; +pub const SMB_NTDBG_CONTROL_BREAK: u32 = 0x40010008; +pub const SMB_NTDBG_COMMAND_EXCEPTION: u32 = 0x40010009; +pub const SMB_NTRPC_NT_UUID_LOCAL_ONLY: u32 = 0x40020056; +pub const SMB_NTRPC_NT_SEND_INCOMPLETE: u32 = 0x400200af; +pub const SMB_NTSTATUS_CTX_CDM_CONNECT: u32 = 0x400a0004; +pub const SMB_NTSTATUS_CTX_CDM_DISCONNECT: u32 = 0x400a0005; +pub const SMB_NTSTATUS_SXS_RELEASE_ACTIVATION_CONTEXT: u32 = 0x4015000d; +pub const SMB_NTSTATUS_RECOVERY_NOT_NEEDED: u32 = 0x40190034; +pub const SMB_NTSTATUS_RM_ALREADY_STARTED: u32 = 0x40190035; +pub const SMB_NTSTATUS_LOG_NO_RESTART: u32 = 0x401a000c; +pub const SMB_NTSTATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST: u32 = 0x401b00ec; +pub const SMB_NTSTATUS_GRAPHICS_PARTIAL_DATA_POPULATED: u32 = 0x401e000a; +pub const SMB_NTSTATUS_GRAPHICS_DRIVER_MISMATCH: u32 = 0x401e0117; +pub const SMB_NTSTATUS_GRAPHICS_MODE_NOT_PINNED: u32 = 0x401e0307; +pub const SMB_NTSTATUS_GRAPHICS_NO_PREFERRED_MODE: u32 = 0x401e031e; +pub const SMB_NTSTATUS_GRAPHICS_DATASET_IS_EMPTY: u32 = 0x401e034b; +pub const SMB_NTSTATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET: u32 = 0x401e034c; +pub const SMB_NTSTATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED: u32 = 0x401e0351; +pub const SMB_NTSTATUS_GRAPHICS_UNKNOWN_CHILD_STATUS: u32 = 0x401e042f; +pub const SMB_NTSTATUS_GRAPHICS_LEADLINK_START_DEFERRED: u32 = 0x401e0437; +pub const SMB_NTSTATUS_GRAPHICS_POLLING_TOO_FREQUENTLY: u32 = 0x401e0439; +pub const SMB_NTSTATUS_GRAPHICS_START_DEFERRED: u32 = 0x401e043a; +pub const SMB_NTSTATUS_NDIS_INDICATION_REQUIRED: u32 = 0x40230001; +pub const SMB_NTSTATUS_GUARD_PAGE_VIOLATION: u32 = 0x80000001; +pub const SMB_NTSTATUS_DATATYPE_MISALIGNMENT: u32 = 0x80000002; +pub const SMB_NTSTATUS_BREAKPOINT: u32 = 0x80000003; +pub const SMB_NTSTATUS_SINGLE_STEP: u32 = 0x80000004; +pub const SMB_NTSTATUS_BUFFER_OVERFLOW: u32 = 0x80000005; +pub const SMB_NTSTATUS_NO_MORE_FILES: u32 = 0x80000006; +pub const SMB_NTSTATUS_WAKE_SYSTEM_DEBUGGER: u32 = 0x80000007; +pub const SMB_NTSTATUS_HANDLES_CLOSED: u32 = 0x8000000a; +pub const SMB_NTSTATUS_NO_INHERITANCE: u32 = 0x8000000b; +pub const SMB_NTSTATUS_GUID_SUBSTITUTION_MADE: u32 = 0x8000000c; +pub const SMB_NTSTATUS_PARTIAL_COPY: u32 = 0x8000000d; +pub const SMB_NTSTATUS_DEVICE_PAPER_EMPTY: u32 = 0x8000000e; +pub const SMB_NTSTATUS_DEVICE_POWERED_OFF: u32 = 0x8000000f; +pub const SMB_NTSTATUS_DEVICE_OFF_LINE: u32 = 0x80000010; +pub const SMB_NTSTATUS_DEVICE_BUSY: u32 = 0x80000011; +pub const SMB_NTSTATUS_NO_MORE_EAS: u32 = 0x80000012; +pub const SMB_NTSTATUS_INVALID_EA_NAME: u32 = 0x80000013; +pub const SMB_NTSTATUS_EA_LIST_INCONSISTENT: u32 = 0x80000014; +pub const SMB_NTSTATUS_INVALID_EA_FLAG: u32 = 0x80000015; +pub const SMB_NTSTATUS_VERIFY_REQUIRED: u32 = 0x80000016; +pub const SMB_NTSTATUS_EXTRANEOUS_INFORMATION: u32 = 0x80000017; +pub const SMB_NTSTATUS_RXACT_COMMIT_NECESSARY: u32 = 0x80000018; +pub const SMB_NTSTATUS_NO_MORE_ENTRIES: u32 = 0x8000001a; +pub const SMB_NTSTATUS_FILEMARK_DETECTED: u32 = 0x8000001b; +pub const SMB_NTSTATUS_MEDIA_CHANGED: u32 = 0x8000001c; +pub const SMB_NTSTATUS_BUS_RESET: u32 = 0x8000001d; +pub const SMB_NTSTATUS_END_OF_MEDIA: u32 = 0x8000001e; +pub const SMB_NTSTATUS_BEGINNING_OF_MEDIA: u32 = 0x8000001f; +pub const SMB_NTSTATUS_MEDIA_CHECK: u32 = 0x80000020; +pub const SMB_NTSTATUS_SETMARK_DETECTED: u32 = 0x80000021; +pub const SMB_NTSTATUS_NO_DATA_DETECTED: u32 = 0x80000022; +pub const SMB_NTSTATUS_REDIRECTOR_HAS_OPEN_HANDLES: u32 = 0x80000023; +pub const SMB_NTSTATUS_SERVER_HAS_OPEN_HANDLES: u32 = 0x80000024; +pub const SMB_NTSTATUS_ALREADY_DISCONNECTED: u32 = 0x80000025; +pub const SMB_NTSTATUS_LONGJUMP: u32 = 0x80000026; +pub const SMB_NTSTATUS_CLEANER_CARTRIDGE_INSTALLED: u32 = 0x80000027; +pub const SMB_NTSTATUS_PLUGPLAY_QUERY_VETOED: u32 = 0x80000028; +pub const SMB_NTSTATUS_UNWIND_CONSOLIDATE: u32 = 0x80000029; +pub const SMB_NTSTATUS_REGISTRY_HIVE_RECOVERED: u32 = 0x8000002a; +pub const SMB_NTSTATUS_DLL_MIGHT_BE_INSECURE: u32 = 0x8000002b; +pub const SMB_NTSTATUS_DLL_MIGHT_BE_INCOMPATIBLE: u32 = 0x8000002c; +pub const SMB_NTSTATUS_STOPPED_ON_SYMLINK: u32 = 0x8000002d; +pub const SMB_NTSTATUS_DEVICE_REQUIRES_CLEANING: u32 = 0x80000288; +pub const SMB_NTSTATUS_DEVICE_DOOR_OPEN: u32 = 0x80000289; +pub const SMB_NTSTATUS_DATA_LOST_REPAIR: u32 = 0x80000803; +pub const SMB_NTDBG_EXCEPTION_NOT_HANDLED: u32 = 0x80010001; +pub const SMB_NTSTATUS_CLUSTER_NODE_ALREADY_UP: u32 = 0x80130001; +pub const SMB_NTSTATUS_CLUSTER_NODE_ALREADY_DOWN: u32 = 0x80130002; +pub const SMB_NTSTATUS_CLUSTER_NETWORK_ALREADY_ONLINE: u32 = 0x80130003; +pub const SMB_NTSTATUS_CLUSTER_NETWORK_ALREADY_OFFLINE: u32 = 0x80130004; +pub const SMB_NTSTATUS_CLUSTER_NODE_ALREADY_MEMBER: u32 = 0x80130005; +pub const SMB_NTSTATUS_COULD_NOT_RESIZE_LOG: u32 = 0x80190009; +pub const SMB_NTSTATUS_NO_TXF_METADATA: u32 = 0x80190029; +pub const SMB_NTSTATUS_CANT_RECOVER_WITH_HANDLE_OPEN: u32 = 0x80190031; +pub const SMB_NTSTATUS_TXF_METADATA_ALREADY_PRESENT: u32 = 0x80190041; +pub const SMB_NTSTATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET: u32 = 0x80190042; +pub const SMB_NTSTATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED: u32 = 0x801b00eb; +pub const SMB_NTSTATUS_FLT_BUFFER_TOO_SMALL: u32 = 0x801c0001; +pub const SMB_NTSTATUS_FVE_PARTIAL_METADATA: u32 = 0x80210001; +pub const SMB_NTSTATUS_FVE_TRANSIENT_STATE: u32 = 0x80210002; +pub const SMB_NTSTATUS_UNSUCCESSFUL: u32 = 0xc0000001; +pub const SMB_NTSTATUS_NOT_IMPLEMENTED: u32 = 0xc0000002; +pub const SMB_NTSTATUS_INVALID_INFO_CLASS: u32 = 0xc0000003; +pub const SMB_NTSTATUS_INFO_LENGTH_MISMATCH: u32 = 0xc0000004; +pub const SMB_NTSTATUS_ACCESS_VIOLATION: u32 = 0xc0000005; +pub const SMB_NTSTATUS_IN_PAGE_ERROR: u32 = 0xc0000006; +pub const SMB_NTSTATUS_PAGEFILE_QUOTA: u32 = 0xc0000007; +pub const SMB_NTSTATUS_INVALID_HANDLE: u32 = 0xc0000008; +pub const SMB_NTSTATUS_BAD_INITIAL_STACK: u32 = 0xc0000009; +pub const SMB_NTSTATUS_BAD_INITIAL_PC: u32 = 0xc000000a; +pub const SMB_NTSTATUS_INVALID_CID: u32 = 0xc000000b; +pub const SMB_NTSTATUS_TIMER_NOT_CANCELED: u32 = 0xc000000c; +pub const SMB_NTSTATUS_INVALID_PARAMETER: u32 = 0xc000000d; +pub const SMB_NTSTATUS_NO_SUCH_DEVICE: u32 = 0xc000000e; +pub const SMB_NTSTATUS_NO_SUCH_FILE: u32 = 0xc000000f; +pub const SMB_NTSTATUS_INVALID_DEVICE_REQUEST: u32 = 0xc0000010; +pub const SMB_NTSTATUS_END_OF_FILE: u32 = 0xc0000011; +pub const SMB_NTSTATUS_WRONG_VOLUME: u32 = 0xc0000012; +pub const SMB_NTSTATUS_NO_MEDIA_IN_DEVICE: u32 = 0xc0000013; +pub const SMB_NTSTATUS_UNRECOGNIZED_MEDIA: u32 = 0xc0000014; +pub const SMB_NTSTATUS_NONEXISTENT_SECTOR: u32 = 0xc0000015; +pub const SMB_NTSTATUS_MORE_PROCESSING_REQUIRED: u32 = 0xc0000016; +pub const SMB_NTSTATUS_NO_MEMORY: u32 = 0xc0000017; +pub const SMB_NTSTATUS_CONFLICTING_ADDRESSES: u32 = 0xc0000018; +pub const SMB_NTSTATUS_NOT_MAPPED_VIEW: u32 = 0xc0000019; +pub const SMB_NTSTATUS_UNABLE_TO_FREE_VM: u32 = 0xc000001a; +pub const SMB_NTSTATUS_UNABLE_TO_DELETE_SECTION: u32 = 0xc000001b; +pub const SMB_NTSTATUS_INVALID_SYSTEM_SERVICE: u32 = 0xc000001c; +pub const SMB_NTSTATUS_ILLEGAL_INSTRUCTION: u32 = 0xc000001d; +pub const SMB_NTSTATUS_INVALID_LOCK_SEQUENCE: u32 = 0xc000001e; +pub const SMB_NTSTATUS_INVALID_VIEW_SIZE: u32 = 0xc000001f; +pub const SMB_NTSTATUS_INVALID_FILE_FOR_SECTION: u32 = 0xc0000020; +pub const SMB_NTSTATUS_ALREADY_COMMITTED: u32 = 0xc0000021; +pub const SMB_NTSTATUS_ACCESS_DENIED: u32 = 0xc0000022; +pub const SMB_NTSTATUS_BUFFER_TOO_SMALL: u32 = 0xc0000023; +pub const SMB_NTSTATUS_OBJECT_TYPE_MISMATCH: u32 = 0xc0000024; +pub const SMB_NTSTATUS_NONCONTINUABLE_EXCEPTION: u32 = 0xc0000025; +pub const SMB_NTSTATUS_INVALID_DISPOSITION: u32 = 0xc0000026; +pub const SMB_NTSTATUS_UNWIND: u32 = 0xc0000027; +pub const SMB_NTSTATUS_BAD_STACK: u32 = 0xc0000028; +pub const SMB_NTSTATUS_INVALID_UNWIND_TARGET: u32 = 0xc0000029; +pub const SMB_NTSTATUS_NOT_LOCKED: u32 = 0xc000002a; +pub const SMB_NTSTATUS_PARITY_ERROR: u32 = 0xc000002b; +pub const SMB_NTSTATUS_UNABLE_TO_DECOMMIT_VM: u32 = 0xc000002c; +pub const SMB_NTSTATUS_NOT_COMMITTED: u32 = 0xc000002d; +pub const SMB_NTSTATUS_INVALID_PORT_ATTRIBUTES: u32 = 0xc000002e; +pub const SMB_NTSTATUS_PORT_MESSAGE_TOO_LONG: u32 = 0xc000002f; +pub const SMB_NTSTATUS_INVALID_PARAMETER_MIX: u32 = 0xc0000030; +pub const SMB_NTSTATUS_INVALID_QUOTA_LOWER: u32 = 0xc0000031; +pub const SMB_NTSTATUS_DISK_CORRUPT_ERROR: u32 = 0xc0000032; +pub const SMB_NTSTATUS_OBJECT_NAME_INVALID: u32 = 0xc0000033; +pub const SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND: u32 = 0xc0000034; +pub const SMB_NTSTATUS_OBJECT_NAME_COLLISION: u32 = 0xc0000035; +pub const SMB_NTSTATUS_PORT_DISCONNECTED: u32 = 0xc0000037; +pub const SMB_NTSTATUS_DEVICE_ALREADY_ATTACHED: u32 = 0xc0000038; +pub const SMB_NTSTATUS_OBJECT_PATH_INVALID: u32 = 0xc0000039; +pub const SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND: u32 = 0xc000003a; +pub const SMB_NTSTATUS_OBJECT_PATH_SYNTAX_BAD: u32 = 0xc000003b; +pub const SMB_NTSTATUS_DATA_OVERRUN: u32 = 0xc000003c; +pub const SMB_NTSTATUS_DATA_LATE_ERROR: u32 = 0xc000003d; +pub const SMB_NTSTATUS_DATA_ERROR: u32 = 0xc000003e; +pub const SMB_NTSTATUS_CRC_ERROR: u32 = 0xc000003f; +pub const SMB_NTSTATUS_SECTION_TOO_BIG: u32 = 0xc0000040; +pub const SMB_NTSTATUS_PORT_CONNECTION_REFUSED: u32 = 0xc0000041; +pub const SMB_NTSTATUS_INVALID_PORT_HANDLE: u32 = 0xc0000042; +pub const SMB_NTSTATUS_SHARING_VIOLATION: u32 = 0xc0000043; +pub const SMB_NTSTATUS_QUOTA_EXCEEDED: u32 = 0xc0000044; +pub const SMB_NTSTATUS_INVALID_PAGE_PROTECTION: u32 = 0xc0000045; +pub const SMB_NTSTATUS_MUTANT_NOT_OWNED: u32 = 0xc0000046; +pub const SMB_NTSTATUS_SEMAPHORE_LIMIT_EXCEEDED: u32 = 0xc0000047; +pub const SMB_NTSTATUS_PORT_ALREADY_SET: u32 = 0xc0000048; +pub const SMB_NTSTATUS_SECTION_NOT_IMAGE: u32 = 0xc0000049; +pub const SMB_NTSTATUS_SUSPEND_COUNT_EXCEEDED: u32 = 0xc000004a; +pub const SMB_NTSTATUS_THREAD_IS_TERMINATING: u32 = 0xc000004b; +pub const SMB_NTSTATUS_BAD_WORKING_SET_LIMIT: u32 = 0xc000004c; +pub const SMB_NTSTATUS_INCOMPATIBLE_FILE_MAP: u32 = 0xc000004d; +pub const SMB_NTSTATUS_SECTION_PROTECTION: u32 = 0xc000004e; +pub const SMB_NTSTATUS_EAS_NOT_SUPPORTED: u32 = 0xc000004f; +pub const SMB_NTSTATUS_EA_TOO_LARGE: u32 = 0xc0000050; +pub const SMB_NTSTATUS_NONEXISTENT_EA_ENTRY: u32 = 0xc0000051; +pub const SMB_NTSTATUS_NO_EAS_ON_FILE: u32 = 0xc0000052; +pub const SMB_NTSTATUS_EA_CORRUPT_ERROR: u32 = 0xc0000053; +pub const SMB_NTSTATUS_FILE_LOCK_CONFLICT: u32 = 0xc0000054; +pub const SMB_NTSTATUS_LOCK_NOT_GRANTED: u32 = 0xc0000055; +pub const SMB_NTSTATUS_DELETE_PENDING: u32 = 0xc0000056; +pub const SMB_NTSTATUS_CTL_FILE_NOT_SUPPORTED: u32 = 0xc0000057; +pub const SMB_NTSTATUS_UNKNOWN_REVISION: u32 = 0xc0000058; +pub const SMB_NTSTATUS_REVISION_MISMATCH: u32 = 0xc0000059; +pub const SMB_NTSTATUS_INVALID_OWNER: u32 = 0xc000005a; +pub const SMB_NTSTATUS_INVALID_PRIMARY_GROUP: u32 = 0xc000005b; +pub const SMB_NTSTATUS_NO_IMPERSONATION_TOKEN: u32 = 0xc000005c; +pub const SMB_NTSTATUS_CANT_DISABLE_MANDATORY: u32 = 0xc000005d; +pub const SMB_NTSTATUS_NO_LOGON_SERVERS: u32 = 0xc000005e; +pub const SMB_NTSTATUS_NO_SUCH_LOGON_SESSION: u32 = 0xc000005f; +pub const SMB_NTSTATUS_NO_SUCH_PRIVILEGE: u32 = 0xc0000060; +pub const SMB_NTSTATUS_PRIVILEGE_NOT_HELD: u32 = 0xc0000061; +pub const SMB_NTSTATUS_INVALID_ACCOUNT_NAME: u32 = 0xc0000062; +pub const SMB_NTSTATUS_USER_EXISTS: u32 = 0xc0000063; +pub const SMB_NTSTATUS_NO_SUCH_USER: u32 = 0xc0000064; +pub const SMB_NTSTATUS_GROUP_EXISTS: u32 = 0xc0000065; +pub const SMB_NTSTATUS_NO_SUCH_GROUP: u32 = 0xc0000066; +pub const SMB_NTSTATUS_MEMBER_IN_GROUP: u32 = 0xc0000067; +pub const SMB_NTSTATUS_MEMBER_NOT_IN_GROUP: u32 = 0xc0000068; +pub const SMB_NTSTATUS_LAST_ADMIN: u32 = 0xc0000069; +pub const SMB_NTSTATUS_WRONG_PASSWORD: u32 = 0xc000006a; +pub const SMB_NTSTATUS_ILL_FORMED_PASSWORD: u32 = 0xc000006b; +pub const SMB_NTSTATUS_PASSWORD_RESTRICTION: u32 = 0xc000006c; +pub const SMB_NTSTATUS_LOGON_FAILURE: u32 = 0xc000006d; +pub const SMB_NTSTATUS_ACCOUNT_RESTRICTION: u32 = 0xc000006e; +pub const SMB_NTSTATUS_INVALID_LOGON_HOURS: u32 = 0xc000006f; +pub const SMB_NTSTATUS_INVALID_WORKSTATION: u32 = 0xc0000070; +pub const SMB_NTSTATUS_PASSWORD_EXPIRED: u32 = 0xc0000071; +pub const SMB_NTSTATUS_ACCOUNT_DISABLED: u32 = 0xc0000072; +pub const SMB_NTSTATUS_NONE_MAPPED: u32 = 0xc0000073; +pub const SMB_NTSTATUS_TOO_MANY_LUIDS_REQUESTED: u32 = 0xc0000074; +pub const SMB_NTSTATUS_LUIDS_EXHAUSTED: u32 = 0xc0000075; +pub const SMB_NTSTATUS_INVALID_SUB_AUTHORITY: u32 = 0xc0000076; +pub const SMB_NTSTATUS_INVALID_ACL: u32 = 0xc0000077; +pub const SMB_NTSTATUS_INVALID_SID: u32 = 0xc0000078; +pub const SMB_NTSTATUS_INVALID_SECURITY_DESCR: u32 = 0xc0000079; +pub const SMB_NTSTATUS_PROCEDURE_NOT_FOUND: u32 = 0xc000007a; +pub const SMB_NTSTATUS_INVALID_IMAGE_FORMAT: u32 = 0xc000007b; +pub const SMB_NTSTATUS_NO_TOKEN: u32 = 0xc000007c; +pub const SMB_NTSTATUS_BAD_INHERITANCE_ACL: u32 = 0xc000007d; +pub const SMB_NTSTATUS_RANGE_NOT_LOCKED: u32 = 0xc000007e; +pub const SMB_NTSTATUS_DISK_FULL: u32 = 0xc000007f; +pub const SMB_NTSTATUS_SERVER_DISABLED: u32 = 0xc0000080; +pub const SMB_NTSTATUS_SERVER_NOT_DISABLED: u32 = 0xc0000081; +pub const SMB_NTSTATUS_TOO_MANY_GUIDS_REQUESTED: u32 = 0xc0000082; +pub const SMB_NTSTATUS_GUIDS_EXHAUSTED: u32 = 0xc0000083; +pub const SMB_NTSTATUS_INVALID_ID_AUTHORITY: u32 = 0xc0000084; +pub const SMB_NTSTATUS_AGENTS_EXHAUSTED: u32 = 0xc0000085; +pub const SMB_NTSTATUS_INVALID_VOLUME_LABEL: u32 = 0xc0000086; +pub const SMB_NTSTATUS_SECTION_NOT_EXTENDED: u32 = 0xc0000087; +pub const SMB_NTSTATUS_NOT_MAPPED_DATA: u32 = 0xc0000088; +pub const SMB_NTSTATUS_RESOURCE_DATA_NOT_FOUND: u32 = 0xc0000089; +pub const SMB_NTSTATUS_RESOURCE_TYPE_NOT_FOUND: u32 = 0xc000008a; +pub const SMB_NTSTATUS_RESOURCE_NAME_NOT_FOUND: u32 = 0xc000008b; +pub const SMB_NTSTATUS_ARRAY_BOUNDS_EXCEEDED: u32 = 0xc000008c; +pub const SMB_NTSTATUS_FLOAT_DENORMAL_OPERAND: u32 = 0xc000008d; +pub const SMB_NTSTATUS_FLOAT_DIVIDE_BY_ZERO: u32 = 0xc000008e; +pub const SMB_NTSTATUS_FLOAT_INEXACT_RESULT: u32 = 0xc000008f; +pub const SMB_NTSTATUS_FLOAT_INVALID_OPERATION: u32 = 0xc0000090; +pub const SMB_NTSTATUS_FLOAT_OVERFLOW: u32 = 0xc0000091; +pub const SMB_NTSTATUS_FLOAT_STACK_CHECK: u32 = 0xc0000092; +pub const SMB_NTSTATUS_FLOAT_UNDERFLOW: u32 = 0xc0000093; +pub const SMB_NTSTATUS_INTEGER_DIVIDE_BY_ZERO: u32 = 0xc0000094; +pub const SMB_NTSTATUS_INTEGER_OVERFLOW: u32 = 0xc0000095; +pub const SMB_NTSTATUS_PRIVILEGED_INSTRUCTION: u32 = 0xc0000096; +pub const SMB_NTSTATUS_TOO_MANY_PAGING_FILES: u32 = 0xc0000097; +pub const SMB_NTSTATUS_FILE_INVALID: u32 = 0xc0000098; +pub const SMB_NTSTATUS_ALLOTTED_SPACE_EXCEEDED: u32 = 0xc0000099; +pub const SMB_NTSTATUS_INSUFFICIENT_RESOURCES: u32 = 0xc000009a; +pub const SMB_NTSTATUS_DFS_EXIT_PATH_FOUND: u32 = 0xc000009b; +pub const SMB_NTSTATUS_DEVICE_DATA_ERROR: u32 = 0xc000009c; +pub const SMB_NTSTATUS_DEVICE_NOT_CONNECTED: u32 = 0xc000009d; +pub const SMB_NTSTATUS_FREE_VM_NOT_AT_BASE: u32 = 0xc000009f; +pub const SMB_NTSTATUS_MEMORY_NOT_ALLOCATED: u32 = 0xc00000a0; +pub const SMB_NTSTATUS_WORKING_SET_QUOTA: u32 = 0xc00000a1; +pub const SMB_NTSTATUS_MEDIA_WRITE_PROTECTED: u32 = 0xc00000a2; +pub const SMB_NTSTATUS_DEVICE_NOT_READY: u32 = 0xc00000a3; +pub const SMB_NTSTATUS_INVALID_GROUP_ATTRIBUTES: u32 = 0xc00000a4; +pub const SMB_NTSTATUS_BAD_IMPERSONATION_LEVEL: u32 = 0xc00000a5; +pub const SMB_NTSTATUS_CANT_OPEN_ANONYMOUS: u32 = 0xc00000a6; +pub const SMB_NTSTATUS_BAD_VALIDATION_CLASS: u32 = 0xc00000a7; +pub const SMB_NTSTATUS_BAD_TOKEN_TYPE: u32 = 0xc00000a8; +pub const SMB_NTSTATUS_BAD_MASTER_BOOT_RECORD: u32 = 0xc00000a9; +pub const SMB_NTSTATUS_INSTRUCTION_MISALIGNMENT: u32 = 0xc00000aa; +pub const SMB_NTSTATUS_INSTANCE_NOT_AVAILABLE: u32 = 0xc00000ab; +pub const SMB_NTSTATUS_PIPE_NOT_AVAILABLE: u32 = 0xc00000ac; +pub const SMB_NTSTATUS_INVALID_PIPE_STATE: u32 = 0xc00000ad; +pub const SMB_NTSTATUS_PIPE_BUSY: u32 = 0xc00000ae; +pub const SMB_NTSTATUS_ILLEGAL_FUNCTION: u32 = 0xc00000af; +pub const SMB_NTSTATUS_PIPE_DISCONNECTED: u32 = 0xc00000b0; +pub const SMB_NTSTATUS_PIPE_CLOSING: u32 = 0xc00000b1; +pub const SMB_NTSTATUS_PIPE_CONNECTED: u32 = 0xc00000b2; +pub const SMB_NTSTATUS_PIPE_LISTENING: u32 = 0xc00000b3; +pub const SMB_NTSTATUS_INVALID_READ_MODE: u32 = 0xc00000b4; +pub const SMB_NTSTATUS_IO_TIMEOUT: u32 = 0xc00000b5; +pub const SMB_NTSTATUS_FILE_FORCED_CLOSED: u32 = 0xc00000b6; +pub const SMB_NTSTATUS_PROFILING_NOT_STARTED: u32 = 0xc00000b7; +pub const SMB_NTSTATUS_PROFILING_NOT_STOPPED: u32 = 0xc00000b8; +pub const SMB_NTSTATUS_COULD_NOT_INTERPRET: u32 = 0xc00000b9; +pub const SMB_NTSTATUS_FILE_IS_A_DIRECTORY: u32 = 0xc00000ba; +pub const SMB_NTSTATUS_NOT_SUPPORTED: u32 = 0xc00000bb; +pub const SMB_NTSTATUS_REMOTE_NOT_LISTENING: u32 = 0xc00000bc; +pub const SMB_NTSTATUS_DUPLICATE_NAME: u32 = 0xc00000bd; +pub const SMB_NTSTATUS_BAD_NETWORK_PATH: u32 = 0xc00000be; +pub const SMB_NTSTATUS_NETWORK_BUSY: u32 = 0xc00000bf; +pub const SMB_NTSTATUS_DEVICE_DOES_NOT_EXIST: u32 = 0xc00000c0; +pub const SMB_NTSTATUS_TOO_MANY_COMMANDS: u32 = 0xc00000c1; +pub const SMB_NTSTATUS_ADAPTER_HARDWARE_ERROR: u32 = 0xc00000c2; +pub const SMB_NTSTATUS_INVALID_NETWORK_RESPONSE: u32 = 0xc00000c3; +pub const SMB_NTSTATUS_UNEXPECTED_NETWORK_ERROR: u32 = 0xc00000c4; +pub const SMB_NTSTATUS_BAD_REMOTE_ADAPTER: u32 = 0xc00000c5; +pub const SMB_NTSTATUS_PRINT_QUEUE_FULL: u32 = 0xc00000c6; +pub const SMB_NTSTATUS_NO_SPOOL_SPACE: u32 = 0xc00000c7; +pub const SMB_NTSTATUS_PRINT_CANCELLED: u32 = 0xc00000c8; +pub const SMB_NTSTATUS_NETWORK_NAME_DELETED: u32 = 0xc00000c9; +pub const SMB_NTSTATUS_NETWORK_ACCESS_DENIED: u32 = 0xc00000ca; +pub const SMB_NTSTATUS_BAD_DEVICE_TYPE: u32 = 0xc00000cb; +pub const SMB_NTSTATUS_BAD_NETWORK_NAME: u32 = 0xc00000cc; +pub const SMB_NTSTATUS_TOO_MANY_NAMES: u32 = 0xc00000cd; +pub const SMB_NTSTATUS_TOO_MANY_SESSIONS: u32 = 0xc00000ce; +pub const SMB_NTSTATUS_SHARING_PAUSED: u32 = 0xc00000cf; +pub const SMB_NTSTATUS_REQUEST_NOT_ACCEPTED: u32 = 0xc00000d0; +pub const SMB_NTSTATUS_REDIRECTOR_PAUSED: u32 = 0xc00000d1; +pub const SMB_NTSTATUS_NET_WRITE_FAULT: u32 = 0xc00000d2; +pub const SMB_NTSTATUS_PROFILING_AT_LIMIT: u32 = 0xc00000d3; +pub const SMB_NTSTATUS_NOT_SAME_DEVICE: u32 = 0xc00000d4; +pub const SMB_NTSTATUS_FILE_RENAMED: u32 = 0xc00000d5; +pub const SMB_NTSTATUS_VIRTUAL_CIRCUIT_CLOSED: u32 = 0xc00000d6; +pub const SMB_NTSTATUS_NO_SECURITY_ON_OBJECT: u32 = 0xc00000d7; +pub const SMB_NTSTATUS_CANT_WAIT: u32 = 0xc00000d8; +pub const SMB_NTSTATUS_PIPE_EMPTY: u32 = 0xc00000d9; +pub const SMB_NTSTATUS_CANT_ACCESS_DOMAIN_INFO: u32 = 0xc00000da; +pub const SMB_NTSTATUS_CANT_TERMINATE_SELF: u32 = 0xc00000db; +pub const SMB_NTSTATUS_INVALID_SERVER_STATE: u32 = 0xc00000dc; +pub const SMB_NTSTATUS_INVALID_DOMAIN_STATE: u32 = 0xc00000dd; +pub const SMB_NTSTATUS_INVALID_DOMAIN_ROLE: u32 = 0xc00000de; +pub const SMB_NTSTATUS_NO_SUCH_DOMAIN: u32 = 0xc00000df; +pub const SMB_NTSTATUS_DOMAIN_EXISTS: u32 = 0xc00000e0; +pub const SMB_NTSTATUS_DOMAIN_LIMIT_EXCEEDED: u32 = 0xc00000e1; +pub const SMB_NTSTATUS_OPLOCK_NOT_GRANTED: u32 = 0xc00000e2; +pub const SMB_NTSTATUS_INVALID_OPLOCK_PROTOCOL: u32 = 0xc00000e3; +pub const SMB_NTSTATUS_INTERNAL_DB_CORRUPTION: u32 = 0xc00000e4; +pub const SMB_NTSTATUS_INTERNAL_ERROR: u32 = 0xc00000e5; +pub const SMB_NTSTATUS_GENERIC_NOT_MAPPED: u32 = 0xc00000e6; +pub const SMB_NTSTATUS_BAD_DESCRIPTOR_FORMAT: u32 = 0xc00000e7; +pub const SMB_NTSTATUS_INVALID_USER_BUFFER: u32 = 0xc00000e8; +pub const SMB_NTSTATUS_UNEXPECTED_IO_ERROR: u32 = 0xc00000e9; +pub const SMB_NTSTATUS_UNEXPECTED_MM_CREATE_ERR: u32 = 0xc00000ea; +pub const SMB_NTSTATUS_UNEXPECTED_MM_MAP_ERROR: u32 = 0xc00000eb; +pub const SMB_NTSTATUS_UNEXPECTED_MM_EXTEND_ERR: u32 = 0xc00000ec; +pub const SMB_NTSTATUS_NOT_LOGON_PROCESS: u32 = 0xc00000ed; +pub const SMB_NTSTATUS_LOGON_SESSION_EXISTS: u32 = 0xc00000ee; +pub const SMB_NTSTATUS_INVALID_PARAMETER_1: u32 = 0xc00000ef; +pub const SMB_NTSTATUS_INVALID_PARAMETER_2: u32 = 0xc00000f0; +pub const SMB_NTSTATUS_INVALID_PARAMETER_3: u32 = 0xc00000f1; +pub const SMB_NTSTATUS_INVALID_PARAMETER_4: u32 = 0xc00000f2; +pub const SMB_NTSTATUS_INVALID_PARAMETER_5: u32 = 0xc00000f3; +pub const SMB_NTSTATUS_INVALID_PARAMETER_6: u32 = 0xc00000f4; +pub const SMB_NTSTATUS_INVALID_PARAMETER_7: u32 = 0xc00000f5; +pub const SMB_NTSTATUS_INVALID_PARAMETER_8: u32 = 0xc00000f6; +pub const SMB_NTSTATUS_INVALID_PARAMETER_9: u32 = 0xc00000f7; +pub const SMB_NTSTATUS_INVALID_PARAMETER_10: u32 = 0xc00000f8; +pub const SMB_NTSTATUS_INVALID_PARAMETER_11: u32 = 0xc00000f9; +pub const SMB_NTSTATUS_INVALID_PARAMETER_12: u32 = 0xc00000fa; +pub const SMB_NTSTATUS_REDIRECTOR_NOT_STARTED: u32 = 0xc00000fb; +pub const SMB_NTSTATUS_REDIRECTOR_STARTED: u32 = 0xc00000fc; +pub const SMB_NTSTATUS_STACK_OVERFLOW: u32 = 0xc00000fd; +pub const SMB_NTSTATUS_NO_SUCH_PACKAGE: u32 = 0xc00000fe; +pub const SMB_NTSTATUS_BAD_FUNCTION_TABLE: u32 = 0xc00000ff; +pub const SMB_NTSTATUS_VARIABLE_NOT_FOUND: u32 = 0xc0000100; +pub const SMB_NTSTATUS_DIRECTORY_NOT_EMPTY: u32 = 0xc0000101; +pub const SMB_NTSTATUS_FILE_CORRUPT_ERROR: u32 = 0xc0000102; +pub const SMB_NTSTATUS_NOT_A_DIRECTORY: u32 = 0xc0000103; +pub const SMB_NTSTATUS_BAD_LOGON_SESSION_STATE: u32 = 0xc0000104; +pub const SMB_NTSTATUS_LOGON_SESSION_COLLISION: u32 = 0xc0000105; +pub const SMB_NTSTATUS_NAME_TOO_LONG: u32 = 0xc0000106; +pub const SMB_NTSTATUS_FILES_OPEN: u32 = 0xc0000107; +pub const SMB_NTSTATUS_CONNECTION_IN_USE: u32 = 0xc0000108; +pub const SMB_NTSTATUS_MESSAGE_NOT_FOUND: u32 = 0xc0000109; +pub const SMB_NTSTATUS_PROCESS_IS_TERMINATING: u32 = 0xc000010a; +pub const SMB_NTSTATUS_INVALID_LOGON_TYPE: u32 = 0xc000010b; +pub const SMB_NTSTATUS_NO_GUID_TRANSLATION: u32 = 0xc000010c; +pub const SMB_NTSTATUS_CANNOT_IMPERSONATE: u32 = 0xc000010d; +pub const SMB_NTSTATUS_IMAGE_ALREADY_LOADED: u32 = 0xc000010e; +pub const SMB_NTSTATUS_NO_LDT: u32 = 0xc0000117; +pub const SMB_NTSTATUS_INVALID_LDT_SIZE: u32 = 0xc0000118; +pub const SMB_NTSTATUS_INVALID_LDT_OFFSET: u32 = 0xc0000119; +pub const SMB_NTSTATUS_INVALID_LDT_DESCRIPTOR: u32 = 0xc000011a; +pub const SMB_NTSTATUS_INVALID_IMAGE_NE_FORMAT: u32 = 0xc000011b; +pub const SMB_NTSTATUS_RXACT_INVALID_STATE: u32 = 0xc000011c; +pub const SMB_NTSTATUS_RXACT_COMMIT_FAILURE: u32 = 0xc000011d; +pub const SMB_NTSTATUS_MAPPED_FILE_SIZE_ZERO: u32 = 0xc000011e; +pub const SMB_NTSTATUS_TOO_MANY_OPENED_FILES: u32 = 0xc000011f; +pub const SMB_NTSTATUS_CANCELLED: u32 = 0xc0000120; +pub const SMB_NTSTATUS_CANNOT_DELETE: u32 = 0xc0000121; +pub const SMB_NTSTATUS_INVALID_COMPUTER_NAME: u32 = 0xc0000122; +pub const SMB_NTSTATUS_FILE_DELETED: u32 = 0xc0000123; +pub const SMB_NTSTATUS_SPECIAL_ACCOUNT: u32 = 0xc0000124; +pub const SMB_NTSTATUS_SPECIAL_GROUP: u32 = 0xc0000125; +pub const SMB_NTSTATUS_SPECIAL_USER: u32 = 0xc0000126; +pub const SMB_NTSTATUS_MEMBERS_PRIMARY_GROUP: u32 = 0xc0000127; +pub const SMB_NTSTATUS_FILE_CLOSED: u32 = 0xc0000128; +pub const SMB_NTSTATUS_TOO_MANY_THREADS: u32 = 0xc0000129; +pub const SMB_NTSTATUS_THREAD_NOT_IN_PROCESS: u32 = 0xc000012a; +pub const SMB_NTSTATUS_TOKEN_ALREADY_IN_USE: u32 = 0xc000012b; +pub const SMB_NTSTATUS_PAGEFILE_QUOTA_EXCEEDED: u32 = 0xc000012c; +pub const SMB_NTSTATUS_COMMITMENT_LIMIT: u32 = 0xc000012d; +pub const SMB_NTSTATUS_INVALID_IMAGE_LE_FORMAT: u32 = 0xc000012e; +pub const SMB_NTSTATUS_INVALID_IMAGE_NOT_MZ: u32 = 0xc000012f; +pub const SMB_NTSTATUS_INVALID_IMAGE_PROTECT: u32 = 0xc0000130; +pub const SMB_NTSTATUS_INVALID_IMAGE_WIN_16: u32 = 0xc0000131; +pub const SMB_NTSTATUS_LOGON_SERVER_CONFLICT: u32 = 0xc0000132; +pub const SMB_NTSTATUS_TIME_DIFFERENCE_AT_DC: u32 = 0xc0000133; +pub const SMB_NTSTATUS_SYNCHRONIZATION_REQUIRED: u32 = 0xc0000134; +pub const SMB_NTSTATUS_DLL_NOT_FOUND: u32 = 0xc0000135; +pub const SMB_NTSTATUS_OPEN_FAILED: u32 = 0xc0000136; +pub const SMB_NTSTATUS_IO_PRIVILEGE_FAILED: u32 = 0xc0000137; +pub const SMB_NTSTATUS_ORDINAL_NOT_FOUND: u32 = 0xc0000138; +pub const SMB_NTSTATUS_ENTRYPOINT_NOT_FOUND: u32 = 0xc0000139; +pub const SMB_NTSTATUS_CONTROL_C_EXIT: u32 = 0xc000013a; +pub const SMB_NTSTATUS_LOCAL_DISCONNECT: u32 = 0xc000013b; +pub const SMB_NTSTATUS_REMOTE_DISCONNECT: u32 = 0xc000013c; +pub const SMB_NTSTATUS_REMOTE_RESOURCES: u32 = 0xc000013d; +pub const SMB_NTSTATUS_LINK_FAILED: u32 = 0xc000013e; +pub const SMB_NTSTATUS_LINK_TIMEOUT: u32 = 0xc000013f; +pub const SMB_NTSTATUS_INVALID_CONNECTION: u32 = 0xc0000140; +pub const SMB_NTSTATUS_INVALID_ADDRESS: u32 = 0xc0000141; +pub const SMB_NTSTATUS_DLL_INIT_FAILED: u32 = 0xc0000142; +pub const SMB_NTSTATUS_MISSING_SYSTEMFILE: u32 = 0xc0000143; +pub const SMB_NTSTATUS_UNHANDLED_EXCEPTION: u32 = 0xc0000144; +pub const SMB_NTSTATUS_APP_INIT_FAILURE: u32 = 0xc0000145; +pub const SMB_NTSTATUS_PAGEFILE_CREATE_FAILED: u32 = 0xc0000146; +pub const SMB_NTSTATUS_NO_PAGEFILE: u32 = 0xc0000147; +pub const SMB_NTSTATUS_INVALID_LEVEL: u32 = 0xc0000148; +pub const SMB_NTSTATUS_WRONG_PASSWORD_CORE: u32 = 0xc0000149; +pub const SMB_NTSTATUS_ILLEGAL_FLOAT_CONTEXT: u32 = 0xc000014a; +pub const SMB_NTSTATUS_PIPE_BROKEN: u32 = 0xc000014b; +pub const SMB_NTSTATUS_REGISTRY_CORRUPT: u32 = 0xc000014c; +pub const SMB_NTSTATUS_REGISTRY_IO_FAILED: u32 = 0xc000014d; +pub const SMB_NTSTATUS_NO_EVENT_PAIR: u32 = 0xc000014e; +pub const SMB_NTSTATUS_UNRECOGNIZED_VOLUME: u32 = 0xc000014f; +pub const SMB_NTSTATUS_SERIAL_NO_DEVICE_INITED: u32 = 0xc0000150; +pub const SMB_NTSTATUS_NO_SUCH_ALIAS: u32 = 0xc0000151; +pub const SMB_NTSTATUS_MEMBER_NOT_IN_ALIAS: u32 = 0xc0000152; +pub const SMB_NTSTATUS_MEMBER_IN_ALIAS: u32 = 0xc0000153; +pub const SMB_NTSTATUS_ALIAS_EXISTS: u32 = 0xc0000154; +pub const SMB_NTSTATUS_LOGON_NOT_GRANTED: u32 = 0xc0000155; +pub const SMB_NTSTATUS_TOO_MANY_SECRETS: u32 = 0xc0000156; +pub const SMB_NTSTATUS_SECRET_TOO_LONG: u32 = 0xc0000157; +pub const SMB_NTSTATUS_INTERNAL_DB_ERROR: u32 = 0xc0000158; +pub const SMB_NTSTATUS_FULLSCREEN_MODE: u32 = 0xc0000159; +pub const SMB_NTSTATUS_TOO_MANY_CONTEXT_IDS: u32 = 0xc000015a; +pub const SMB_NTSTATUS_LOGON_TYPE_NOT_GRANTED: u32 = 0xc000015b; +pub const SMB_NTSTATUS_NOT_REGISTRY_FILE: u32 = 0xc000015c; +pub const SMB_NTSTATUS_NT_CROSS_ENCRYPTION_REQUIRED: u32 = 0xc000015d; +pub const SMB_NTSTATUS_DOMAIN_CTRLR_CONFIG_ERROR: u32 = 0xc000015e; +pub const SMB_NTSTATUS_FT_MISSING_MEMBER: u32 = 0xc000015f; +pub const SMB_NTSTATUS_ILL_FORMED_SERVICE_ENTRY: u32 = 0xc0000160; +pub const SMB_NTSTATUS_ILLEGAL_CHARACTER: u32 = 0xc0000161; +pub const SMB_NTSTATUS_UNMAPPABLE_CHARACTER: u32 = 0xc0000162; +pub const SMB_NTSTATUS_UNDEFINED_CHARACTER: u32 = 0xc0000163; +pub const SMB_NTSTATUS_FLOPPY_VOLUME: u32 = 0xc0000164; +pub const SMB_NTSTATUS_FLOPPY_ID_MARK_NOT_FOUND: u32 = 0xc0000165; +pub const SMB_NTSTATUS_FLOPPY_WRONG_CYLINDER: u32 = 0xc0000166; +pub const SMB_NTSTATUS_FLOPPY_UNKNOWN_ERROR: u32 = 0xc0000167; +pub const SMB_NTSTATUS_FLOPPY_BAD_REGISTERS: u32 = 0xc0000168; +pub const SMB_NTSTATUS_DISK_RECALIBRATE_FAILED: u32 = 0xc0000169; +pub const SMB_NTSTATUS_DISK_OPERATION_FAILED: u32 = 0xc000016a; +pub const SMB_NTSTATUS_DISK_RESET_FAILED: u32 = 0xc000016b; +pub const SMB_NTSTATUS_SHARED_IRQ_BUSY: u32 = 0xc000016c; +pub const SMB_NTSTATUS_FT_ORPHANING: u32 = 0xc000016d; +pub const SMB_NTSTATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT: u32 = 0xc000016e; +pub const SMB_NTSTATUS_PARTITION_FAILURE: u32 = 0xc0000172; +pub const SMB_NTSTATUS_INVALID_BLOCK_LENGTH: u32 = 0xc0000173; +pub const SMB_NTSTATUS_DEVICE_NOT_PARTITIONED: u32 = 0xc0000174; +pub const SMB_NTSTATUS_UNABLE_TO_LOCK_MEDIA: u32 = 0xc0000175; +pub const SMB_NTSTATUS_UNABLE_TO_UNLOAD_MEDIA: u32 = 0xc0000176; +pub const SMB_NTSTATUS_EOM_OVERFLOW: u32 = 0xc0000177; +pub const SMB_NTSTATUS_NO_MEDIA: u32 = 0xc0000178; +pub const SMB_NTSTATUS_NO_SUCH_MEMBER: u32 = 0xc000017a; +pub const SMB_NTSTATUS_INVALID_MEMBER: u32 = 0xc000017b; +pub const SMB_NTSTATUS_KEY_DELETED: u32 = 0xc000017c; +pub const SMB_NTSTATUS_NO_LOG_SPACE: u32 = 0xc000017d; +pub const SMB_NTSTATUS_TOO_MANY_SIDS: u32 = 0xc000017e; +pub const SMB_NTSTATUS_LM_CROSS_ENCRYPTION_REQUIRED: u32 = 0xc000017f; +pub const SMB_NTSTATUS_KEY_HAS_CHILDREN: u32 = 0xc0000180; +pub const SMB_NTSTATUS_CHILD_MUST_BE_VOLATILE: u32 = 0xc0000181; +pub const SMB_NTSTATUS_DEVICE_CONFIGURATION_ERROR: u32 = 0xc0000182; +pub const SMB_NTSTATUS_DRIVER_INTERNAL_ERROR: u32 = 0xc0000183; +pub const SMB_NTSTATUS_INVALID_DEVICE_STATE: u32 = 0xc0000184; +pub const SMB_NTSTATUS_IO_DEVICE_ERROR: u32 = 0xc0000185; +pub const SMB_NTSTATUS_DEVICE_PROTOCOL_ERROR: u32 = 0xc0000186; +pub const SMB_NTSTATUS_BACKUP_CONTROLLER: u32 = 0xc0000187; +pub const SMB_NTSTATUS_LOG_FILE_FULL: u32 = 0xc0000188; +pub const SMB_NTSTATUS_TOO_LATE: u32 = 0xc0000189; +pub const SMB_NTSTATUS_NO_TRUST_LSA_SECRET: u32 = 0xc000018a; +pub const SMB_NTSTATUS_NO_TRUST_SAM_ACCOUNT: u32 = 0xc000018b; +pub const SMB_NTSTATUS_TRUSTED_DOMAIN_FAILURE: u32 = 0xc000018c; +pub const SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE: u32 = 0xc000018d; +pub const SMB_NTSTATUS_EVENTLOG_FILE_CORRUPT: u32 = 0xc000018e; +pub const SMB_NTSTATUS_EVENTLOG_CANT_START: u32 = 0xc000018f; +pub const SMB_NTSTATUS_TRUST_FAILURE: u32 = 0xc0000190; +pub const SMB_NTSTATUS_MUTANT_LIMIT_EXCEEDED: u32 = 0xc0000191; +pub const SMB_NTSTATUS_NETLOGON_NOT_STARTED: u32 = 0xc0000192; +pub const SMB_NTSTATUS_ACCOUNT_EXPIRED: u32 = 0xc0000193; +pub const SMB_NTSTATUS_POSSIBLE_DEADLOCK: u32 = 0xc0000194; +pub const SMB_NTSTATUS_NETWORK_CREDENTIAL_CONFLICT: u32 = 0xc0000195; +pub const SMB_NTSTATUS_REMOTE_SESSION_LIMIT: u32 = 0xc0000196; +pub const SMB_NTSTATUS_EVENTLOG_FILE_CHANGED: u32 = 0xc0000197; +pub const SMB_NTSTATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT: u32 = 0xc0000198; +pub const SMB_NTSTATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT: u32 = 0xc0000199; +pub const SMB_NTSTATUS_NOLOGON_SERVER_TRUST_ACCOUNT: u32 = 0xc000019a; +pub const SMB_NTSTATUS_DOMAIN_TRUST_INCONSISTENT: u32 = 0xc000019b; +pub const SMB_NTSTATUS_FS_DRIVER_REQUIRED: u32 = 0xc000019c; +pub const SMB_NTSTATUS_IMAGE_ALREADY_LOADED_AS_DLL: u32 = 0xc000019d; +pub const SMB_NTSTATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING: u32 = 0xc000019e; +pub const SMB_NTSTATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME: u32 = 0xc000019f; +pub const SMB_NTSTATUS_SECURITY_STREAM_IS_INCONSISTENT: u32 = 0xc00001a0; +pub const SMB_NTSTATUS_INVALID_LOCK_RANGE: u32 = 0xc00001a1; +pub const SMB_NTSTATUS_INVALID_ACE_CONDITION: u32 = 0xc00001a2; +pub const SMB_NTSTATUS_IMAGE_SUBSYSTEM_NOT_PRESENT: u32 = 0xc00001a3; +pub const SMB_NTSTATUS_NOTIFICATION_GUID_ALREADY_DEFINED: u32 = 0xc00001a4; +pub const SMB_NTSTATUS_NETWORK_OPEN_RESTRICTION: u32 = 0xc0000201; +pub const SMB_NTSTATUS_NO_USER_SESSION_KEY: u32 = 0xc0000202; +pub const SMB_NTSTATUS_USER_SESSION_DELETED: u32 = 0xc0000203; +pub const SMB_NTSTATUS_RESOURCE_LANG_NOT_FOUND: u32 = 0xc0000204; +pub const SMB_NTSTATUS_INSUFF_SERVER_RESOURCES: u32 = 0xc0000205; +pub const SMB_NTSTATUS_INVALID_BUFFER_SIZE: u32 = 0xc0000206; +pub const SMB_NTSTATUS_INVALID_ADDRESS_COMPONENT: u32 = 0xc0000207; +pub const SMB_NTSTATUS_INVALID_ADDRESS_WILDCARD: u32 = 0xc0000208; +pub const SMB_NTSTATUS_TOO_MANY_ADDRESSES: u32 = 0xc0000209; +pub const SMB_NTSTATUS_ADDRESS_ALREADY_EXISTS: u32 = 0xc000020a; +pub const SMB_NTSTATUS_ADDRESS_CLOSED: u32 = 0xc000020b; +pub const SMB_NTSTATUS_CONNECTION_DISCONNECTED: u32 = 0xc000020c; +pub const SMB_NTSTATUS_CONNECTION_RESET: u32 = 0xc000020d; +pub const SMB_NTSTATUS_TOO_MANY_NODES: u32 = 0xc000020e; +pub const SMB_NTSTATUS_TRANSACTION_ABORTED: u32 = 0xc000020f; +pub const SMB_NTSTATUS_TRANSACTION_TIMED_OUT: u32 = 0xc0000210; +pub const SMB_NTSTATUS_TRANSACTION_NO_RELEASE: u32 = 0xc0000211; +pub const SMB_NTSTATUS_TRANSACTION_NO_MATCH: u32 = 0xc0000212; +pub const SMB_NTSTATUS_TRANSACTION_RESPONDED: u32 = 0xc0000213; +pub const SMB_NTSTATUS_TRANSACTION_INVALID_ID: u32 = 0xc0000214; +pub const SMB_NTSTATUS_TRANSACTION_INVALID_TYPE: u32 = 0xc0000215; +pub const SMB_NTSTATUS_NOT_SERVER_SESSION: u32 = 0xc0000216; +pub const SMB_NTSTATUS_NOT_CLIENT_SESSION: u32 = 0xc0000217; +pub const SMB_NTSTATUS_CANNOT_LOAD_REGISTRY_FILE: u32 = 0xc0000218; +pub const SMB_NTSTATUS_DEBUG_ATTACH_FAILED: u32 = 0xc0000219; +pub const SMB_NTSTATUS_SYSTEM_PROCESS_TERMINATED: u32 = 0xc000021a; +pub const SMB_NTSTATUS_DATA_NOT_ACCEPTED: u32 = 0xc000021b; +pub const SMB_NTSTATUS_NO_BROWSER_SERVERS_FOUND: u32 = 0xc000021c; +pub const SMB_NTSTATUS_VDM_HARD_ERROR: u32 = 0xc000021d; +pub const SMB_NTSTATUS_DRIVER_CANCEL_TIMEOUT: u32 = 0xc000021e; +pub const SMB_NTSTATUS_REPLY_MESSAGE_MISMATCH: u32 = 0xc000021f; +pub const SMB_NTSTATUS_MAPPED_ALIGNMENT: u32 = 0xc0000220; +pub const SMB_NTSTATUS_IMAGE_CHECKSUM_MISMATCH: u32 = 0xc0000221; +pub const SMB_NTSTATUS_LOST_WRITEBEHIND_DATA: u32 = 0xc0000222; +pub const SMB_NTSTATUS_CLIENT_SERVER_PARAMETERS_INVALID: u32 = 0xc0000223; +pub const SMB_NTSTATUS_PASSWORD_MUST_CHANGE: u32 = 0xc0000224; +pub const SMB_NTSTATUS_NOT_FOUND: u32 = 0xc0000225; +pub const SMB_NTSTATUS_NOT_TINY_STREAM: u32 = 0xc0000226; +pub const SMB_NTSTATUS_RECOVERY_FAILURE: u32 = 0xc0000227; +pub const SMB_NTSTATUS_STACK_OVERFLOW_READ: u32 = 0xc0000228; +pub const SMB_NTSTATUS_FAIL_CHECK: u32 = 0xc0000229; +pub const SMB_NTSTATUS_DUPLICATE_OBJECTID: u32 = 0xc000022a; +pub const SMB_NTSTATUS_OBJECTID_EXISTS: u32 = 0xc000022b; +pub const SMB_NTSTATUS_CONVERT_TO_LARGE: u32 = 0xc000022c; +pub const SMB_NTSTATUS_RETRY: u32 = 0xc000022d; +pub const SMB_NTSTATUS_FOUND_OUT_OF_SCOPE: u32 = 0xc000022e; +pub const SMB_NTSTATUS_ALLOCATE_BUCKET: u32 = 0xc000022f; +pub const SMB_NTSTATUS_PROPSET_NOT_FOUND: u32 = 0xc0000230; +pub const SMB_NTSTATUS_MARSHALL_OVERFLOW: u32 = 0xc0000231; +pub const SMB_NTSTATUS_INVALID_VARIANT: u32 = 0xc0000232; +pub const SMB_NTSTATUS_DOMAIN_CONTROLLER_NOT_FOUND: u32 = 0xc0000233; +pub const SMB_NTSTATUS_ACCOUNT_LOCKED_OUT: u32 = 0xc0000234; +pub const SMB_NTSTATUS_HANDLE_NOT_CLOSABLE: u32 = 0xc0000235; +pub const SMB_NTSTATUS_CONNECTION_REFUSED: u32 = 0xc0000236; +pub const SMB_NTSTATUS_GRACEFUL_DISCONNECT: u32 = 0xc0000237; +pub const SMB_NTSTATUS_ADDRESS_ALREADY_ASSOCIATED: u32 = 0xc0000238; +pub const SMB_NTSTATUS_ADDRESS_NOT_ASSOCIATED: u32 = 0xc0000239; +pub const SMB_NTSTATUS_CONNECTION_INVALID: u32 = 0xc000023a; +pub const SMB_NTSTATUS_CONNECTION_ACTIVE: u32 = 0xc000023b; +pub const SMB_NTSTATUS_NETWORK_UNREACHABLE: u32 = 0xc000023c; +pub const SMB_NTSTATUS_HOST_UNREACHABLE: u32 = 0xc000023d; +pub const SMB_NTSTATUS_PROTOCOL_UNREACHABLE: u32 = 0xc000023e; +pub const SMB_NTSTATUS_PORT_UNREACHABLE: u32 = 0xc000023f; +pub const SMB_NTSTATUS_REQUEST_ABORTED: u32 = 0xc0000240; +pub const SMB_NTSTATUS_CONNECTION_ABORTED: u32 = 0xc0000241; +pub const SMB_NTSTATUS_BAD_COMPRESSION_BUFFER: u32 = 0xc0000242; +pub const SMB_NTSTATUS_USER_MAPPED_FILE: u32 = 0xc0000243; +pub const SMB_NTSTATUS_AUDIT_FAILED: u32 = 0xc0000244; +pub const SMB_NTSTATUS_TIMER_RESOLUTION_NOT_SET: u32 = 0xc0000245; +pub const SMB_NTSTATUS_CONNECTION_COUNT_LIMIT: u32 = 0xc0000246; +pub const SMB_NTSTATUS_LOGIN_TIME_RESTRICTION: u32 = 0xc0000247; +pub const SMB_NTSTATUS_LOGIN_WKSTA_RESTRICTION: u32 = 0xc0000248; +pub const SMB_NTSTATUS_IMAGE_MP_UP_MISMATCH: u32 = 0xc0000249; +pub const SMB_NTSTATUS_INSUFFICIENT_LOGON_INFO: u32 = 0xc0000250; +pub const SMB_NTSTATUS_BAD_DLL_ENTRYPOINT: u32 = 0xc0000251; +pub const SMB_NTSTATUS_BAD_SERVICE_ENTRYPOINT: u32 = 0xc0000252; +pub const SMB_NTSTATUS_LPC_REPLY_LOST: u32 = 0xc0000253; +pub const SMB_NTSTATUS_IP_ADDRESS_CONFLICT1: u32 = 0xc0000254; +pub const SMB_NTSTATUS_IP_ADDRESS_CONFLICT2: u32 = 0xc0000255; +pub const SMB_NTSTATUS_REGISTRY_QUOTA_LIMIT: u32 = 0xc0000256; +pub const SMB_NTSTATUS_PATH_NOT_COVERED: u32 = 0xc0000257; +pub const SMB_NTSTATUS_NO_CALLBACK_ACTIVE: u32 = 0xc0000258; +pub const SMB_NTSTATUS_LICENSE_QUOTA_EXCEEDED: u32 = 0xc0000259; +pub const SMB_NTSTATUS_PWD_TOO_SHORT: u32 = 0xc000025a; +pub const SMB_NTSTATUS_PWD_TOO_RECENT: u32 = 0xc000025b; +pub const SMB_NTSTATUS_PWD_HISTORY_CONFLICT: u32 = 0xc000025c; +pub const SMB_NTSTATUS_PLUGPLAY_NO_DEVICE: u32 = 0xc000025e; +pub const SMB_NTSTATUS_UNSUPPORTED_COMPRESSION: u32 = 0xc000025f; +pub const SMB_NTSTATUS_INVALID_HW_PROFILE: u32 = 0xc0000260; +pub const SMB_NTSTATUS_INVALID_PLUGPLAY_DEVICE_PATH: u32 = 0xc0000261; +pub const SMB_NTSTATUS_DRIVER_ORDINAL_NOT_FOUND: u32 = 0xc0000262; +pub const SMB_NTSTATUS_DRIVER_ENTRYPOINT_NOT_FOUND: u32 = 0xc0000263; +pub const SMB_NTSTATUS_RESOURCE_NOT_OWNED: u32 = 0xc0000264; +pub const SMB_NTSTATUS_TOO_MANY_LINKS: u32 = 0xc0000265; +pub const SMB_NTSTATUS_QUOTA_LIST_INCONSISTENT: u32 = 0xc0000266; +pub const SMB_NTSTATUS_FILE_IS_OFFLINE: u32 = 0xc0000267; +pub const SMB_NTSTATUS_EVALUATION_EXPIRATION: u32 = 0xc0000268; +pub const SMB_NTSTATUS_ILLEGAL_DLL_RELOCATION: u32 = 0xc0000269; +pub const SMB_NTSTATUS_LICENSE_VIOLATION: u32 = 0xc000026a; +pub const SMB_NTSTATUS_DLL_INIT_FAILED_LOGOFF: u32 = 0xc000026b; +pub const SMB_NTSTATUS_DRIVER_UNABLE_TO_LOAD: u32 = 0xc000026c; +pub const SMB_NTSTATUS_DFS_UNAVAILABLE: u32 = 0xc000026d; +pub const SMB_NTSTATUS_VOLUME_DISMOUNTED: u32 = 0xc000026e; +pub const SMB_NTSTATUS_WX86_INTERNAL_ERROR: u32 = 0xc000026f; +pub const SMB_NTSTATUS_WX86_FLOAT_STACK_CHECK: u32 = 0xc0000270; +pub const SMB_NTSTATUS_VALIDATE_CONTINUE: u32 = 0xc0000271; +pub const SMB_NTSTATUS_NO_MATCH: u32 = 0xc0000272; +pub const SMB_NTSTATUS_NO_MORE_MATCHES: u32 = 0xc0000273; +pub const SMB_NTSTATUS_NOT_A_REPARSE_POINT: u32 = 0xc0000275; +pub const SMB_NTSTATUS_IO_REPARSE_TAG_INVALID: u32 = 0xc0000276; +pub const SMB_NTSTATUS_IO_REPARSE_TAG_MISMATCH: u32 = 0xc0000277; +pub const SMB_NTSTATUS_IO_REPARSE_DATA_INVALID: u32 = 0xc0000278; +pub const SMB_NTSTATUS_IO_REPARSE_TAG_NOT_HANDLED: u32 = 0xc0000279; +pub const SMB_NTSTATUS_REPARSE_POINT_NOT_RESOLVED: u32 = 0xc0000280; +pub const SMB_NTSTATUS_DIRECTORY_IS_A_REPARSE_POINT: u32 = 0xc0000281; +pub const SMB_NTSTATUS_RANGE_LIST_CONFLICT: u32 = 0xc0000282; +pub const SMB_NTSTATUS_SOURCE_ELEMENT_EMPTY: u32 = 0xc0000283; +pub const SMB_NTSTATUS_DESTINATION_ELEMENT_FULL: u32 = 0xc0000284; +pub const SMB_NTSTATUS_ILLEGAL_ELEMENT_ADDRESS: u32 = 0xc0000285; +pub const SMB_NTSTATUS_MAGAZINE_NOT_PRESENT: u32 = 0xc0000286; +pub const SMB_NTSTATUS_REINITIALIZATION_NEEDED: u32 = 0xc0000287; +pub const SMB_NTSTATUS_ENCRYPTION_FAILED: u32 = 0xc000028a; +pub const SMB_NTSTATUS_DECRYPTION_FAILED: u32 = 0xc000028b; +pub const SMB_NTSTATUS_RANGE_NOT_FOUND: u32 = 0xc000028c; +pub const SMB_NTSTATUS_NO_RECOVERY_POLICY: u32 = 0xc000028d; +pub const SMB_NTSTATUS_NO_EFS: u32 = 0xc000028e; +pub const SMB_NTSTATUS_WRONG_EFS: u32 = 0xc000028f; +pub const SMB_NTSTATUS_NO_USER_KEYS: u32 = 0xc0000290; +pub const SMB_NTSTATUS_FILE_NOT_ENCRYPTED: u32 = 0xc0000291; +pub const SMB_NTSTATUS_NOT_EXPORT_FORMAT: u32 = 0xc0000292; +pub const SMB_NTSTATUS_FILE_ENCRYPTED: u32 = 0xc0000293; +pub const SMB_NTSTATUS_WMI_GUID_NOT_FOUND: u32 = 0xc0000295; +pub const SMB_NTSTATUS_WMI_INSTANCE_NOT_FOUND: u32 = 0xc0000296; +pub const SMB_NTSTATUS_WMI_ITEMID_NOT_FOUND: u32 = 0xc0000297; +pub const SMB_NTSTATUS_WMI_TRY_AGAIN: u32 = 0xc0000298; +pub const SMB_NTSTATUS_SHARED_POLICY: u32 = 0xc0000299; +pub const SMB_NTSTATUS_POLICY_OBJECT_NOT_FOUND: u32 = 0xc000029a; +pub const SMB_NTSTATUS_POLICY_ONLY_IN_DS: u32 = 0xc000029b; +pub const SMB_NTSTATUS_VOLUME_NOT_UPGRADED: u32 = 0xc000029c; +pub const SMB_NTSTATUS_REMOTE_STORAGE_NOT_ACTIVE: u32 = 0xc000029d; +pub const SMB_NTSTATUS_REMOTE_STORAGE_MEDIA_ERROR: u32 = 0xc000029e; +pub const SMB_NTSTATUS_NO_TRACKING_SERVICE: u32 = 0xc000029f; +pub const SMB_NTSTATUS_SERVER_SID_MISMATCH: u32 = 0xc00002a0; +pub const SMB_NTSTATUS_DS_NO_ATTRIBUTE_OR_VALUE: u32 = 0xc00002a1; +pub const SMB_NTSTATUS_DS_INVALID_ATTRIBUTE_SYNTAX: u32 = 0xc00002a2; +pub const SMB_NTSTATUS_DS_ATTRIBUTE_TYPE_UNDEFINED: u32 = 0xc00002a3; +pub const SMB_NTSTATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS: u32 = 0xc00002a4; +pub const SMB_NTSTATUS_DS_BUSY: u32 = 0xc00002a5; +pub const SMB_NTSTATUS_DS_UNAVAILABLE: u32 = 0xc00002a6; +pub const SMB_NTSTATUS_DS_NO_RIDS_ALLOCATED: u32 = 0xc00002a7; +pub const SMB_NTSTATUS_DS_NO_MORE_RIDS: u32 = 0xc00002a8; +pub const SMB_NTSTATUS_DS_INCORRECT_ROLE_OWNER: u32 = 0xc00002a9; +pub const SMB_NTSTATUS_DS_RIDMGR_INIT_ERROR: u32 = 0xc00002aa; +pub const SMB_NTSTATUS_DS_OBJ_CLASS_VIOLATION: u32 = 0xc00002ab; +pub const SMB_NTSTATUS_DS_CANT_ON_NON_LEAF: u32 = 0xc00002ac; +pub const SMB_NTSTATUS_DS_CANT_ON_RDN: u32 = 0xc00002ad; +pub const SMB_NTSTATUS_DS_CANT_MOD_OBJ_CLASS: u32 = 0xc00002ae; +pub const SMB_NTSTATUS_DS_CROSS_DOM_MOVE_FAILED: u32 = 0xc00002af; +pub const SMB_NTSTATUS_DS_GC_NOT_AVAILABLE: u32 = 0xc00002b0; +pub const SMB_NTSTATUS_DIRECTORY_SERVICE_REQUIRED: u32 = 0xc00002b1; +pub const SMB_NTSTATUS_REPARSE_ATTRIBUTE_CONFLICT: u32 = 0xc00002b2; +pub const SMB_NTSTATUS_CANT_ENABLE_DENY_ONLY: u32 = 0xc00002b3; +pub const SMB_NTSTATUS_FLOAT_MULTIPLE_FAULTS: u32 = 0xc00002b4; +pub const SMB_NTSTATUS_FLOAT_MULTIPLE_TRAPS: u32 = 0xc00002b5; +pub const SMB_NTSTATUS_DEVICE_REMOVED: u32 = 0xc00002b6; +pub const SMB_NTSTATUS_JOURNAL_DELETE_IN_PROGRESS: u32 = 0xc00002b7; +pub const SMB_NTSTATUS_JOURNAL_NOT_ACTIVE: u32 = 0xc00002b8; +pub const SMB_NTSTATUS_NOINTERFACE: u32 = 0xc00002b9; +pub const SMB_NTSTATUS_DS_ADMIN_LIMIT_EXCEEDED: u32 = 0xc00002c1; +pub const SMB_NTSTATUS_DRIVER_FAILED_SLEEP: u32 = 0xc00002c2; +pub const SMB_NTSTATUS_MUTUAL_AUTHENTICATION_FAILED: u32 = 0xc00002c3; +pub const SMB_NTSTATUS_CORRUPT_SYSTEM_FILE: u32 = 0xc00002c4; +pub const SMB_NTSTATUS_DATATYPE_MISALIGNMENT_ERROR: u32 = 0xc00002c5; +pub const SMB_NTSTATUS_WMI_READ_ONLY: u32 = 0xc00002c6; +pub const SMB_NTSTATUS_WMI_SET_FAILURE: u32 = 0xc00002c7; +pub const SMB_NTSTATUS_COMMITMENT_MINIMUM: u32 = 0xc00002c8; +pub const SMB_NTSTATUS_REG_NAT_CONSUMPTION: u32 = 0xc00002c9; +pub const SMB_NTSTATUS_TRANSPORT_FULL: u32 = 0xc00002ca; +pub const SMB_NTSTATUS_DS_SAM_INIT_FAILURE: u32 = 0xc00002cb; +pub const SMB_NTSTATUS_ONLY_IF_CONNECTED: u32 = 0xc00002cc; +pub const SMB_NTSTATUS_DS_SENSITIVE_GROUP_VIOLATION: u32 = 0xc00002cd; +pub const SMB_NTSTATUS_PNP_RESTART_ENUMERATION: u32 = 0xc00002ce; +pub const SMB_NTSTATUS_JOURNAL_ENTRY_DELETED: u32 = 0xc00002cf; +pub const SMB_NTSTATUS_DS_CANT_MOD_PRIMARYGROUPID: u32 = 0xc00002d0; +pub const SMB_NTSTATUS_SYSTEM_IMAGE_BAD_SIGNATURE: u32 = 0xc00002d1; +pub const SMB_NTSTATUS_PNP_REBOOT_REQUIRED: u32 = 0xc00002d2; +pub const SMB_NTSTATUS_POWER_STATE_INVALID: u32 = 0xc00002d3; +pub const SMB_NTSTATUS_DS_INVALID_GROUP_TYPE: u32 = 0xc00002d4; +pub const SMB_NTSTATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN: u32 = 0xc00002d5; +pub const SMB_NTSTATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN: u32 = 0xc00002d6; +pub const SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER: u32 = 0xc00002d7; +pub const SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER: u32 = 0xc00002d8; +pub const SMB_NTSTATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER: u32 = 0xc00002d9; +pub const SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER: u32 = 0xc00002da; +pub const SMB_NTSTATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER: u32 = 0xc00002db; +pub const SMB_NTSTATUS_DS_HAVE_PRIMARY_MEMBERS: u32 = 0xc00002dc; +pub const SMB_NTSTATUS_WMI_NOT_SUPPORTED: u32 = 0xc00002dd; +pub const SMB_NTSTATUS_INSUFFICIENT_POWER: u32 = 0xc00002de; +pub const SMB_NTSTATUS_SAM_NEED_BOOTKEY_PASSWORD: u32 = 0xc00002df; +pub const SMB_NTSTATUS_SAM_NEED_BOOTKEY_FLOPPY: u32 = 0xc00002e0; +pub const SMB_NTSTATUS_DS_CANT_START: u32 = 0xc00002e1; +pub const SMB_NTSTATUS_DS_INIT_FAILURE: u32 = 0xc00002e2; +pub const SMB_NTSTATUS_SAM_INIT_FAILURE: u32 = 0xc00002e3; +pub const SMB_NTSTATUS_DS_GC_REQUIRED: u32 = 0xc00002e4; +pub const SMB_NTSTATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY: u32 = 0xc00002e5; +pub const SMB_NTSTATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS: u32 = 0xc00002e6; +pub const SMB_NTSTATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED: u32 = 0xc00002e7; +pub const SMB_NTSTATUS_CURRENT_DOMAIN_NOT_ALLOWED: u32 = 0xc00002e9; +pub const SMB_NTSTATUS_CANNOT_MAKE: u32 = 0xc00002ea; +pub const SMB_NTSTATUS_SYSTEM_SHUTDOWN: u32 = 0xc00002eb; +pub const SMB_NTSTATUS_DS_INIT_FAILURE_CONSOLE: u32 = 0xc00002ec; +pub const SMB_NTSTATUS_DS_SAM_INIT_FAILURE_CONSOLE: u32 = 0xc00002ed; +pub const SMB_NTSTATUS_UNFINISHED_CONTEXT_DELETED: u32 = 0xc00002ee; +pub const SMB_NTSTATUS_NO_TGT_REPLY: u32 = 0xc00002ef; +pub const SMB_NTSTATUS_OBJECTID_NOT_FOUND: u32 = 0xc00002f0; +pub const SMB_NTSTATUS_NO_IP_ADDRESSES: u32 = 0xc00002f1; +pub const SMB_NTSTATUS_WRONG_CREDENTIAL_HANDLE: u32 = 0xc00002f2; +pub const SMB_NTSTATUS_CRYPTO_SYSTEM_INVALID: u32 = 0xc00002f3; +pub const SMB_NTSTATUS_MAX_REFERRALS_EXCEEDED: u32 = 0xc00002f4; +pub const SMB_NTSTATUS_MUST_BE_KDC: u32 = 0xc00002f5; +pub const SMB_NTSTATUS_STRONG_CRYPTO_NOT_SUPPORTED: u32 = 0xc00002f6; +pub const SMB_NTSTATUS_TOO_MANY_PRINCIPALS: u32 = 0xc00002f7; +pub const SMB_NTSTATUS_NO_PA_DATA: u32 = 0xc00002f8; +pub const SMB_NTSTATUS_PKINIT_NAME_MISMATCH: u32 = 0xc00002f9; +pub const SMB_NTSTATUS_SMARTCARD_LOGON_REQUIRED: u32 = 0xc00002fa; +pub const SMB_NTSTATUS_KDC_INVALID_REQUEST: u32 = 0xc00002fb; +pub const SMB_NTSTATUS_KDC_UNABLE_TO_REFER: u32 = 0xc00002fc; +pub const SMB_NTSTATUS_KDC_UNKNOWN_ETYPE: u32 = 0xc00002fd; +pub const SMB_NTSTATUS_SHUTDOWN_IN_PROGRESS: u32 = 0xc00002fe; +pub const SMB_NTSTATUS_SERVER_SHUTDOWN_IN_PROGRESS: u32 = 0xc00002ff; +pub const SMB_NTSTATUS_NOT_SUPPORTED_ON_SBS: u32 = 0xc0000300; +pub const SMB_NTSTATUS_WMI_GUID_DISCONNECTED: u32 = 0xc0000301; +pub const SMB_NTSTATUS_WMI_ALREADY_DISABLED: u32 = 0xc0000302; +pub const SMB_NTSTATUS_WMI_ALREADY_ENABLED: u32 = 0xc0000303; +pub const SMB_NTSTATUS_MFT_TOO_FRAGMENTED: u32 = 0xc0000304; +pub const SMB_NTSTATUS_COPY_PROTECTION_FAILURE: u32 = 0xc0000305; +pub const SMB_NTSTATUS_CSS_AUTHENTICATION_FAILURE: u32 = 0xc0000306; +pub const SMB_NTSTATUS_CSS_KEY_NOT_PRESENT: u32 = 0xc0000307; +pub const SMB_NTSTATUS_CSS_KEY_NOT_ESTABLISHED: u32 = 0xc0000308; +pub const SMB_NTSTATUS_CSS_SCRAMBLED_SECTOR: u32 = 0xc0000309; +pub const SMB_NTSTATUS_CSS_REGION_MISMATCH: u32 = 0xc000030a; +pub const SMB_NTSTATUS_CSS_RESETS_EXHAUSTED: u32 = 0xc000030b; +pub const SMB_NTSTATUS_PKINIT_FAILURE: u32 = 0xc0000320; +pub const SMB_NTSTATUS_SMARTCARD_SUBSYSTEM_FAILURE: u32 = 0xc0000321; +pub const SMB_NTSTATUS_NO_KERB_KEY: u32 = 0xc0000322; +pub const SMB_NTSTATUS_HOST_DOWN: u32 = 0xc0000350; +pub const SMB_NTSTATUS_UNSUPPORTED_PREAUTH: u32 = 0xc0000351; +pub const SMB_NTSTATUS_EFS_ALG_BLOB_TOO_BIG: u32 = 0xc0000352; +pub const SMB_NTSTATUS_PORT_NOT_SET: u32 = 0xc0000353; +pub const SMB_NTSTATUS_DEBUGGER_INACTIVE: u32 = 0xc0000354; +pub const SMB_NTSTATUS_DS_VERSION_CHECK_FAILURE: u32 = 0xc0000355; +pub const SMB_NTSTATUS_AUDITING_DISABLED: u32 = 0xc0000356; +pub const SMB_NTSTATUS_PRENT4_MACHINE_ACCOUNT: u32 = 0xc0000357; +pub const SMB_NTSTATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER: u32 = 0xc0000358; +pub const SMB_NTSTATUS_INVALID_IMAGE_WIN_32: u32 = 0xc0000359; +pub const SMB_NTSTATUS_INVALID_IMAGE_WIN_64: u32 = 0xc000035a; +pub const SMB_NTSTATUS_BAD_BINDINGS: u32 = 0xc000035b; +pub const SMB_NTSTATUS_NETWORK_SESSION_EXPIRED: u32 = 0xc000035c; +pub const SMB_NTSTATUS_APPHELP_BLOCK: u32 = 0xc000035d; +pub const SMB_NTSTATUS_ALL_SIDS_FILTERED: u32 = 0xc000035e; +pub const SMB_NTSTATUS_NOT_SAFE_MODE_DRIVER: u32 = 0xc000035f; +pub const SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT: u32 = 0xc0000361; +pub const SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_PATH: u32 = 0xc0000362; +pub const SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER: u32 = 0xc0000363; +pub const SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_OTHER: u32 = 0xc0000364; +pub const SMB_NTSTATUS_FAILED_DRIVER_ENTRY: u32 = 0xc0000365; +pub const SMB_NTSTATUS_DEVICE_ENUMERATION_ERROR: u32 = 0xc0000366; +pub const SMB_NTSTATUS_MOUNT_POINT_NOT_RESOLVED: u32 = 0xc0000368; +pub const SMB_NTSTATUS_INVALID_DEVICE_OBJECT_PARAMETER: u32 = 0xc0000369; +pub const SMB_NTSTATUS_MCA_OCCURED: u32 = 0xc000036a; +pub const SMB_NTSTATUS_DRIVER_BLOCKED_CRITICAL: u32 = 0xc000036b; +pub const SMB_NTSTATUS_DRIVER_BLOCKED: u32 = 0xc000036c; +pub const SMB_NTSTATUS_DRIVER_DATABASE_ERROR: u32 = 0xc000036d; +pub const SMB_NTSTATUS_SYSTEM_HIVE_TOO_LARGE: u32 = 0xc000036e; +pub const SMB_NTSTATUS_INVALID_IMPORT_OF_NON_DLL: u32 = 0xc000036f; +pub const SMB_NTSTATUS_NO_SECRETS: u32 = 0xc0000371; +pub const SMB_NTSTATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY: u32 = 0xc0000372; +pub const SMB_NTSTATUS_FAILED_STACK_SWITCH: u32 = 0xc0000373; +pub const SMB_NTSTATUS_HEAP_CORRUPTION: u32 = 0xc0000374; +pub const SMB_NTSTATUS_SMARTCARD_WRONG_PIN: u32 = 0xc0000380; +pub const SMB_NTSTATUS_SMARTCARD_CARD_BLOCKED: u32 = 0xc0000381; +pub const SMB_NTSTATUS_SMARTCARD_CARD_NOT_AUTHENTICATED: u32 = 0xc0000382; +pub const SMB_NTSTATUS_SMARTCARD_NO_CARD: u32 = 0xc0000383; +pub const SMB_NTSTATUS_SMARTCARD_NO_KEY_CONTAINER: u32 = 0xc0000384; +pub const SMB_NTSTATUS_SMARTCARD_NO_CERTIFICATE: u32 = 0xc0000385; +pub const SMB_NTSTATUS_SMARTCARD_NO_KEYSET: u32 = 0xc0000386; +pub const SMB_NTSTATUS_SMARTCARD_IO_ERROR: u32 = 0xc0000387; +pub const SMB_NTSTATUS_DOWNGRADE_DETECTED: u32 = 0xc0000388; +pub const SMB_NTSTATUS_SMARTCARD_CERT_REVOKED: u32 = 0xc0000389; +pub const SMB_NTSTATUS_ISSUING_CA_UNTRUSTED: u32 = 0xc000038a; +pub const SMB_NTSTATUS_REVOCATION_OFFLINE_C: u32 = 0xc000038b; +pub const SMB_NTSTATUS_PKINIT_CLIENT_FAILURE: u32 = 0xc000038c; +pub const SMB_NTSTATUS_SMARTCARD_CERT_EXPIRED: u32 = 0xc000038d; +pub const SMB_NTSTATUS_DRIVER_FAILED_PRIOR_UNLOAD: u32 = 0xc000038e; +pub const SMB_NTSTATUS_SMARTCARD_SILENT_CONTEXT: u32 = 0xc000038f; +pub const SMB_NTSTATUS_PER_USER_TRUST_QUOTA_EXCEEDED: u32 = 0xc0000401; +pub const SMB_NTSTATUS_ALL_USER_TRUST_QUOTA_EXCEEDED: u32 = 0xc0000402; +pub const SMB_NTSTATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED: u32 = 0xc0000403; +pub const SMB_NTSTATUS_DS_NAME_NOT_UNIQUE: u32 = 0xc0000404; +pub const SMB_NTSTATUS_DS_DUPLICATE_ID_FOUND: u32 = 0xc0000405; +pub const SMB_NTSTATUS_DS_GROUP_CONVERSION_ERROR: u32 = 0xc0000406; +pub const SMB_NTSTATUS_VOLSNAP_PREPARE_HIBERNATE: u32 = 0xc0000407; +pub const SMB_NTSTATUS_USER2USER_REQUIRED: u32 = 0xc0000408; +pub const SMB_NTSTATUS_STACK_BUFFER_OVERRUN: u32 = 0xc0000409; +pub const SMB_NTSTATUS_NO_S4U_PROT_SUPPORT: u32 = 0xc000040a; +pub const SMB_NTSTATUS_CROSSREALM_DELEGATION_FAILURE: u32 = 0xc000040b; +pub const SMB_NTSTATUS_REVOCATION_OFFLINE_KDC: u32 = 0xc000040c; +pub const SMB_NTSTATUS_ISSUING_CA_UNTRUSTED_KDC: u32 = 0xc000040d; +pub const SMB_NTSTATUS_KDC_CERT_EXPIRED: u32 = 0xc000040e; +pub const SMB_NTSTATUS_KDC_CERT_REVOKED: u32 = 0xc000040f; +pub const SMB_NTSTATUS_PARAMETER_QUOTA_EXCEEDED: u32 = 0xc0000410; +pub const SMB_NTSTATUS_HIBERNATION_FAILURE: u32 = 0xc0000411; +pub const SMB_NTSTATUS_DELAY_LOAD_FAILED: u32 = 0xc0000412; +pub const SMB_NTSTATUS_AUTHENTICATION_FIREWALL_FAILED: u32 = 0xc0000413; +pub const SMB_NTSTATUS_VDM_DISALLOWED: u32 = 0xc0000414; +pub const SMB_NTSTATUS_HUNG_DISPLAY_DRIVER_THREAD: u32 = 0xc0000415; +pub const SMB_NTSTATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE: u32 = 0xc0000416; +pub const SMB_NTSTATUS_INVALID_CRUNTIME_PARAMETER: u32 = 0xc0000417; +pub const SMB_NTSTATUS_NTLM_BLOCKED: u32 = 0xc0000418; +pub const SMB_NTSTATUS_DS_SRC_SID_EXISTS_IN_FOREST: u32 = 0xc0000419; +pub const SMB_NTSTATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST: u32 = 0xc000041a; +pub const SMB_NTSTATUS_DS_FLAT_NAME_EXISTS_IN_FOREST: u32 = 0xc000041b; +pub const SMB_NTSTATUS_INVALID_USER_PRINCIPAL_NAME: u32 = 0xc000041c; +pub const SMB_NTSTATUS_ASSERTION_FAILURE: u32 = 0xc0000420; +pub const SMB_NTSTATUS_VERIFIER_STOP: u32 = 0xc0000421; +pub const SMB_NTSTATUS_CALLBACK_POP_STACK: u32 = 0xc0000423; +pub const SMB_NTSTATUS_INCOMPATIBLE_DRIVER_BLOCKED: u32 = 0xc0000424; +pub const SMB_NTSTATUS_HIVE_UNLOADED: u32 = 0xc0000425; +pub const SMB_NTSTATUS_COMPRESSION_DISABLED: u32 = 0xc0000426; +pub const SMB_NTSTATUS_FILE_SYSTEM_LIMITATION: u32 = 0xc0000427; +pub const SMB_NTSTATUS_INVALID_IMAGE_HASH: u32 = 0xc0000428; +pub const SMB_NTSTATUS_NOT_CAPABLE: u32 = 0xc0000429; +pub const SMB_NTSTATUS_REQUEST_OUT_OF_SEQUENCE: u32 = 0xc000042a; +pub const SMB_NTSTATUS_IMPLEMENTATION_LIMIT: u32 = 0xc000042b; +pub const SMB_NTSTATUS_ELEVATION_REQUIRED: u32 = 0xc000042c; +pub const SMB_NTSTATUS_NO_SECURITY_CONTEXT: u32 = 0xc000042d; +pub const SMB_NTSTATUS_PKU2U_CERT_FAILURE: u32 = 0xc000042e; +pub const SMB_NTSTATUS_BEYOND_VDL: u32 = 0xc0000432; +pub const SMB_NTSTATUS_ENCOUNTERED_WRITE_IN_PROGRESS: u32 = 0xc0000433; +pub const SMB_NTSTATUS_PTE_CHANGED: u32 = 0xc0000434; +pub const SMB_NTSTATUS_PURGE_FAILED: u32 = 0xc0000435; +pub const SMB_NTSTATUS_CRED_REQUIRES_CONFIRMATION: u32 = 0xc0000440; +pub const SMB_NTSTATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE: u32 = 0xc0000441; +pub const SMB_NTSTATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER: u32 = 0xc0000442; +pub const SMB_NTSTATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE: u32 = 0xc0000443; +pub const SMB_NTSTATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE: u32 = 0xc0000444; +pub const SMB_NTSTATUS_CS_ENCRYPTION_FILE_NOT_CSE: u32 = 0xc0000445; +pub const SMB_NTSTATUS_INVALID_LABEL: u32 = 0xc0000446; +pub const SMB_NTSTATUS_DRIVER_PROCESS_TERMINATED: u32 = 0xc0000450; +pub const SMB_NTSTATUS_AMBIGUOUS_SYSTEM_DEVICE: u32 = 0xc0000451; +pub const SMB_NTSTATUS_SYSTEM_DEVICE_NOT_FOUND: u32 = 0xc0000452; +pub const SMB_NTSTATUS_RESTART_BOOT_APPLICATION: u32 = 0xc0000453; +pub const SMB_NTSTATUS_INSUFFICIENT_NVRAM_RESOURCES: u32 = 0xc0000454; +pub const SMB_NTSTATUS_NO_RANGES_PROCESSED: u32 = 0xc0000460; +pub const SMB_NTSTATUS_DEVICE_FEATURE_NOT_SUPPORTED: u32 = 0xc0000463; +pub const SMB_NTSTATUS_DEVICE_UNREACHABLE: u32 = 0xc0000464; +pub const SMB_NTSTATUS_INVALID_TOKEN: u32 = 0xc0000465; +pub const SMB_NTSTATUS_SERVER_UNAVAILABLE: u32 = 0xc0000466; +pub const SMB_NTSTATUS_INVALID_TASK_NAME: u32 = 0xc0000500; +pub const SMB_NTSTATUS_INVALID_TASK_INDEX: u32 = 0xc0000501; +pub const SMB_NTSTATUS_THREAD_ALREADY_IN_TASK: u32 = 0xc0000502; +pub const SMB_NTSTATUS_CALLBACK_BYPASS: u32 = 0xc0000503; +pub const SMB_NTSTATUS_FAIL_FAST_EXCEPTION: u32 = 0xc0000602; +pub const SMB_NTSTATUS_IMAGE_CERT_REVOKED: u32 = 0xc0000603; +pub const SMB_NTSTATUS_PORT_CLOSED: u32 = 0xc0000700; +pub const SMB_NTSTATUS_MESSAGE_LOST: u32 = 0xc0000701; +pub const SMB_NTSTATUS_INVALID_MESSAGE: u32 = 0xc0000702; +pub const SMB_NTSTATUS_REQUEST_CANCELED: u32 = 0xc0000703; +pub const SMB_NTSTATUS_RECURSIVE_DISPATCH: u32 = 0xc0000704; +pub const SMB_NTSTATUS_LPC_RECEIVE_BUFFER_EXPECTED: u32 = 0xc0000705; +pub const SMB_NTSTATUS_LPC_INVALID_CONNECTION_USAGE: u32 = 0xc0000706; +pub const SMB_NTSTATUS_LPC_REQUESTS_NOT_ALLOWED: u32 = 0xc0000707; +pub const SMB_NTSTATUS_RESOURCE_IN_USE: u32 = 0xc0000708; +pub const SMB_NTSTATUS_HARDWARE_MEMORY_ERROR: u32 = 0xc0000709; +pub const SMB_NTSTATUS_THREADPOOL_HANDLE_EXCEPTION: u32 = 0xc000070a; +pub const SMB_NTSTATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED: u32 = 0xc000070b; +pub const SMB_NTSTATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED: u32 = 0xc000070c; +pub const SMB_NTSTATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED: u32 = 0xc000070d; +pub const SMB_NTSTATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED: u32 = 0xc000070e; +pub const SMB_NTSTATUS_THREADPOOL_RELEASED_DURING_OPERATION: u32 = 0xc000070f; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING: u32 = 0xc0000710; +pub const SMB_NTSTATUS_APC_RETURNED_WHILE_IMPERSONATING: u32 = 0xc0000711; +pub const SMB_NTSTATUS_PROCESS_IS_PROTECTED: u32 = 0xc0000712; +pub const SMB_NTSTATUS_MCA_EXCEPTION: u32 = 0xc0000713; +pub const SMB_NTSTATUS_CERTIFICATE_MAPPING_NOT_UNIQUE: u32 = 0xc0000714; +pub const SMB_NTSTATUS_SYMLINK_CLASS_DISABLED: u32 = 0xc0000715; +pub const SMB_NTSTATUS_INVALID_IDN_NORMALIZATION: u32 = 0xc0000716; +pub const SMB_NTSTATUS_NO_UNICODE_TRANSLATION: u32 = 0xc0000717; +pub const SMB_NTSTATUS_ALREADY_REGISTERED: u32 = 0xc0000718; +pub const SMB_NTSTATUS_CONTEXT_MISMATCH: u32 = 0xc0000719; +pub const SMB_NTSTATUS_PORT_ALREADY_HAS_COMPLETION_LIST: u32 = 0xc000071a; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_THREAD_PRIORITY: u32 = 0xc000071b; +pub const SMB_NTSTATUS_INVALID_THREAD: u32 = 0xc000071c; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_TRANSACTION: u32 = 0xc000071d; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_LDR_LOCK: u32 = 0xc000071e; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_LANG: u32 = 0xc000071f; +pub const SMB_NTSTATUS_CALLBACK_RETURNED_PRI_BACK: u32 = 0xc0000720; +pub const SMB_NTSTATUS_DISK_REPAIR_DISABLED: u32 = 0xc0000800; +pub const SMB_NTSTATUS_DS_DOMAIN_RENAME_IN_PROGRESS: u32 = 0xc0000801; +pub const SMB_NTSTATUS_DISK_QUOTA_EXCEEDED: u32 = 0xc0000802; +pub const SMB_NTSTATUS_CONTENT_BLOCKED: u32 = 0xc0000804; +pub const SMB_NTSTATUS_BAD_CLUSTERS: u32 = 0xc0000805; +pub const SMB_NTSTATUS_VOLUME_DIRTY: u32 = 0xc0000806; +pub const SMB_NTSTATUS_FILE_CHECKED_OUT: u32 = 0xc0000901; +pub const SMB_NTSTATUS_CHECKOUT_REQUIRED: u32 = 0xc0000902; +pub const SMB_NTSTATUS_BAD_FILE_TYPE: u32 = 0xc0000903; +pub const SMB_NTSTATUS_FILE_TOO_LARGE: u32 = 0xc0000904; +pub const SMB_NTSTATUS_FORMS_AUTH_REQUIRED: u32 = 0xc0000905; +pub const SMB_NTSTATUS_VIRUS_INFECTED: u32 = 0xc0000906; +pub const SMB_NTSTATUS_VIRUS_DELETED: u32 = 0xc0000907; +pub const SMB_NTSTATUS_BAD_MCFG_TABLE: u32 = 0xc0000908; +pub const SMB_NTSTATUS_CANNOT_BREAK_OPLOCK: u32 = 0xc0000909; +pub const SMB_NTSTATUS_WOW_ASSERTION: u32 = 0xc0009898; +pub const SMB_NTSTATUS_INVALID_SIGNATURE: u32 = 0xc000a000; +pub const SMB_NTSTATUS_HMAC_NOT_SUPPORTED: u32 = 0xc000a001; +pub const SMB_NTSTATUS_IPSEC_QUEUE_OVERFLOW: u32 = 0xc000a010; +pub const SMB_NTSTATUS_ND_QUEUE_OVERFLOW: u32 = 0xc000a011; +pub const SMB_NTSTATUS_HOPLIMIT_EXCEEDED: u32 = 0xc000a012; +pub const SMB_NTSTATUS_PROTOCOL_NOT_SUPPORTED: u32 = 0xc000a013; +pub const SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED: u32 = 0xc000a080; +pub const SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR: u32 = 0xc000a081; +pub const SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR: u32 = 0xc000a082; +pub const SMB_NTSTATUS_XML_PARSE_ERROR: u32 = 0xc000a083; +pub const SMB_NTSTATUS_XMLDSIG_ERROR: u32 = 0xc000a084; +pub const SMB_NTSTATUS_WRONG_COMPARTMENT: u32 = 0xc000a085; +pub const SMB_NTSTATUS_AUTHIP_FAILURE: u32 = 0xc000a086; +pub const SMB_NTSTATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS: u32 = 0xc000a087; +pub const SMB_NTSTATUS_DS_OID_NOT_FOUND: u32 = 0xc000a088; +pub const SMB_NTSTATUS_HASH_NOT_SUPPORTED: u32 = 0xc000a100; +pub const SMB_NTSTATUS_HASH_NOT_PRESENT: u32 = 0xc000a101; +pub const SMB_NTSTATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED: u32 = 0xc000a2a1; +pub const SMB_NTSTATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED: u32 = 0xc000a2a2; +pub const SMB_NTSTATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED: u32 = 0xc000a2a3; +pub const SMB_NTSTATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED: u32 = 0xc000a2a4; +pub const SMB_NTDBG_NO_STATE_CHANGE: u32 = 0xc0010001; +pub const SMB_NTDBG_APP_NOT_IDLE: u32 = 0xc0010002; +pub const SMB_NTRPC_NT_INVALID_STRING_BINDING: u32 = 0xc0020001; +pub const SMB_NTRPC_NT_WRONG_KIND_OF_BINDING: u32 = 0xc0020002; +pub const SMB_NTRPC_NT_INVALID_BINDING: u32 = 0xc0020003; +pub const SMB_NTRPC_NT_PROTSEQ_NOT_SUPPORTED: u32 = 0xc0020004; +pub const SMB_NTRPC_NT_INVALID_RPC_PROTSEQ: u32 = 0xc0020005; +pub const SMB_NTRPC_NT_INVALID_STRING_UUID: u32 = 0xc0020006; +pub const SMB_NTRPC_NT_INVALID_ENDPOINT_FORMAT: u32 = 0xc0020007; +pub const SMB_NTRPC_NT_INVALID_NET_ADDR: u32 = 0xc0020008; +pub const SMB_NTRPC_NT_NO_ENDPOINT_FOUND: u32 = 0xc0020009; +pub const SMB_NTRPC_NT_INVALID_TIMEOUT: u32 = 0xc002000a; +pub const SMB_NTRPC_NT_OBJECT_NOT_FOUND: u32 = 0xc002000b; +pub const SMB_NTRPC_NT_ALREADY_REGISTERED: u32 = 0xc002000c; +pub const SMB_NTRPC_NT_TYPE_ALREADY_REGISTERED: u32 = 0xc002000d; +pub const SMB_NTRPC_NT_ALREADY_LISTENING: u32 = 0xc002000e; +pub const SMB_NTRPC_NT_NO_PROTSEQS_REGISTERED: u32 = 0xc002000f; +pub const SMB_NTRPC_NT_NOT_LISTENING: u32 = 0xc0020010; +pub const SMB_NTRPC_NT_UNKNOWN_MGR_TYPE: u32 = 0xc0020011; +pub const SMB_NTRPC_NT_UNKNOWN_IF: u32 = 0xc0020012; +pub const SMB_NTRPC_NT_NO_BINDINGS: u32 = 0xc0020013; +pub const SMB_NTRPC_NT_NO_PROTSEQS: u32 = 0xc0020014; +pub const SMB_NTRPC_NT_CANT_CREATE_ENDPOINT: u32 = 0xc0020015; +pub const SMB_NTRPC_NT_OUT_OF_RESOURCES: u32 = 0xc0020016; +pub const SMB_NTRPC_NT_SERVER_UNAVAILABLE: u32 = 0xc0020017; +pub const SMB_NTRPC_NT_SERVER_TOO_BUSY: u32 = 0xc0020018; +pub const SMB_NTRPC_NT_INVALID_NETWORK_OPTIONS: u32 = 0xc0020019; +pub const SMB_NTRPC_NT_NO_CALL_ACTIVE: u32 = 0xc002001a; +pub const SMB_NTRPC_NT_CALL_FAILED: u32 = 0xc002001b; +pub const SMB_NTRPC_NT_CALL_FAILED_DNE: u32 = 0xc002001c; +pub const SMB_NTRPC_NT_PROTOCOL_ERROR: u32 = 0xc002001d; +pub const SMB_NTRPC_NT_UNSUPPORTED_TRANS_SYN: u32 = 0xc002001f; +pub const SMB_NTRPC_NT_UNSUPPORTED_TYPE: u32 = 0xc0020021; +pub const SMB_NTRPC_NT_INVALID_TAG: u32 = 0xc0020022; +pub const SMB_NTRPC_NT_INVALID_BOUND: u32 = 0xc0020023; +pub const SMB_NTRPC_NT_NO_ENTRY_NAME: u32 = 0xc0020024; +pub const SMB_NTRPC_NT_INVALID_NAME_SYNTAX: u32 = 0xc0020025; +pub const SMB_NTRPC_NT_UNSUPPORTED_NAME_SYNTAX: u32 = 0xc0020026; +pub const SMB_NTRPC_NT_UUID_NO_ADDRESS: u32 = 0xc0020028; +pub const SMB_NTRPC_NT_DUPLICATE_ENDPOINT: u32 = 0xc0020029; +pub const SMB_NTRPC_NT_UNKNOWN_AUTHN_TYPE: u32 = 0xc002002a; +pub const SMB_NTRPC_NT_MAX_CALLS_TOO_SMALL: u32 = 0xc002002b; +pub const SMB_NTRPC_NT_STRING_TOO_LONG: u32 = 0xc002002c; +pub const SMB_NTRPC_NT_PROTSEQ_NOT_FOUND: u32 = 0xc002002d; +pub const SMB_NTRPC_NT_PROCNUM_OUT_OF_RANGE: u32 = 0xc002002e; +pub const SMB_NTRPC_NT_BINDING_HAS_NO_AUTH: u32 = 0xc002002f; +pub const SMB_NTRPC_NT_UNKNOWN_AUTHN_SERVICE: u32 = 0xc0020030; +pub const SMB_NTRPC_NT_UNKNOWN_AUTHN_LEVEL: u32 = 0xc0020031; +pub const SMB_NTRPC_NT_INVALID_AUTH_IDENTITY: u32 = 0xc0020032; +pub const SMB_NTRPC_NT_UNKNOWN_AUTHZ_SERVICE: u32 = 0xc0020033; +pub const SMB_NTEPT_NT_INVALID_ENTRY: u32 = 0xc0020034; +pub const SMB_NTEPT_NT_CANT_PERFORM_OP: u32 = 0xc0020035; +pub const SMB_NTEPT_NT_NOT_REGISTERED: u32 = 0xc0020036; +pub const SMB_NTRPC_NT_NOTHING_TO_EXPORT: u32 = 0xc0020037; +pub const SMB_NTRPC_NT_INCOMPLETE_NAME: u32 = 0xc0020038; +pub const SMB_NTRPC_NT_INVALID_VERS_OPTION: u32 = 0xc0020039; +pub const SMB_NTRPC_NT_NO_MORE_MEMBERS: u32 = 0xc002003a; +pub const SMB_NTRPC_NT_NOT_ALL_OBJS_UNEXPORTED: u32 = 0xc002003b; +pub const SMB_NTRPC_NT_INTERFACE_NOT_FOUND: u32 = 0xc002003c; +pub const SMB_NTRPC_NT_ENTRY_ALREADY_EXISTS: u32 = 0xc002003d; +pub const SMB_NTRPC_NT_ENTRY_NOT_FOUND: u32 = 0xc002003e; +pub const SMB_NTRPC_NT_NAME_SERVICE_UNAVAILABLE: u32 = 0xc002003f; +pub const SMB_NTRPC_NT_INVALID_NAF_ID: u32 = 0xc0020040; +pub const SMB_NTRPC_NT_CANNOT_SUPPORT: u32 = 0xc0020041; +pub const SMB_NTRPC_NT_NO_CONTEXT_AVAILABLE: u32 = 0xc0020042; +pub const SMB_NTRPC_NT_INTERNAL_ERROR: u32 = 0xc0020043; +pub const SMB_NTRPC_NT_ZERO_DIVIDE: u32 = 0xc0020044; +pub const SMB_NTRPC_NT_ADDRESS_ERROR: u32 = 0xc0020045; +pub const SMB_NTRPC_NT_FP_DIV_ZERO: u32 = 0xc0020046; +pub const SMB_NTRPC_NT_FP_UNDERFLOW: u32 = 0xc0020047; +pub const SMB_NTRPC_NT_FP_OVERFLOW: u32 = 0xc0020048; +pub const SMB_NTRPC_NT_CALL_IN_PROGRESS: u32 = 0xc0020049; +pub const SMB_NTRPC_NT_NO_MORE_BINDINGS: u32 = 0xc002004a; +pub const SMB_NTRPC_NT_GROUP_MEMBER_NOT_FOUND: u32 = 0xc002004b; +pub const SMB_NTEPT_NT_CANT_CREATE: u32 = 0xc002004c; +pub const SMB_NTRPC_NT_INVALID_OBJECT: u32 = 0xc002004d; +pub const SMB_NTRPC_NT_NO_INTERFACES: u32 = 0xc002004f; +pub const SMB_NTRPC_NT_CALL_CANCELLED: u32 = 0xc0020050; +pub const SMB_NTRPC_NT_BINDING_INCOMPLETE: u32 = 0xc0020051; +pub const SMB_NTRPC_NT_COMM_FAILURE: u32 = 0xc0020052; +pub const SMB_NTRPC_NT_UNSUPPORTED_AUTHN_LEVEL: u32 = 0xc0020053; +pub const SMB_NTRPC_NT_NO_PRINC_NAME: u32 = 0xc0020054; +pub const SMB_NTRPC_NT_NOT_RPC_ERROR: u32 = 0xc0020055; +pub const SMB_NTRPC_NT_SEC_PKG_ERROR: u32 = 0xc0020057; +pub const SMB_NTRPC_NT_NOT_CANCELLED: u32 = 0xc0020058; +pub const SMB_NTRPC_NT_INVALID_ASYNC_HANDLE: u32 = 0xc0020062; +pub const SMB_NTRPC_NT_INVALID_ASYNC_CALL: u32 = 0xc0020063; +pub const SMB_NTRPC_NT_PROXY_ACCESS_DENIED: u32 = 0xc0020064; +pub const SMB_NTRPC_NT_NO_MORE_ENTRIES: u32 = 0xc0030001; +pub const SMB_NTRPC_NT_SS_CHAR_TRANS_OPEN_FAIL: u32 = 0xc0030002; +pub const SMB_NTRPC_NT_SS_CHAR_TRANS_SHORT_FILE: u32 = 0xc0030003; +pub const SMB_NTRPC_NT_SS_IN_NULL_CONTEXT: u32 = 0xc0030004; +pub const SMB_NTRPC_NT_SS_CONTEXT_MISMATCH: u32 = 0xc0030005; +pub const SMB_NTRPC_NT_SS_CONTEXT_DAMAGED: u32 = 0xc0030006; +pub const SMB_NTRPC_NT_SS_HANDLES_MISMATCH: u32 = 0xc0030007; +pub const SMB_NTRPC_NT_SS_CANNOT_GET_CALL_HANDLE: u32 = 0xc0030008; +pub const SMB_NTRPC_NT_NULL_REF_POINTER: u32 = 0xc0030009; +pub const SMB_NTRPC_NT_ENUM_VALUE_OUT_OF_RANGE: u32 = 0xc003000a; +pub const SMB_NTRPC_NT_BYTE_COUNT_TOO_SMALL: u32 = 0xc003000b; +pub const SMB_NTRPC_NT_BAD_STUB_DATA: u32 = 0xc003000c; +pub const SMB_NTRPC_NT_INVALID_ES_ACTION: u32 = 0xc0030059; +pub const SMB_NTRPC_NT_WRONG_ES_VERSION: u32 = 0xc003005a; +pub const SMB_NTRPC_NT_WRONG_STUB_VERSION: u32 = 0xc003005b; +pub const SMB_NTRPC_NT_INVALID_PIPE_OBJECT: u32 = 0xc003005c; +pub const SMB_NTRPC_NT_INVALID_PIPE_OPERATION: u32 = 0xc003005d; +pub const SMB_NTRPC_NT_WRONG_PIPE_VERSION: u32 = 0xc003005e; +pub const SMB_NTRPC_NT_PIPE_CLOSED: u32 = 0xc003005f; +pub const SMB_NTRPC_NT_PIPE_DISCIPLINE_ERROR: u32 = 0xc0030060; +pub const SMB_NTRPC_NT_PIPE_EMPTY: u32 = 0xc0030061; +pub const SMB_NTSTATUS_PNP_BAD_MPS_TABLE: u32 = 0xc0040035; +pub const SMB_NTSTATUS_PNP_TRANSLATION_FAILED: u32 = 0xc0040036; +pub const SMB_NTSTATUS_PNP_IRQ_TRANSLATION_FAILED: u32 = 0xc0040037; +pub const SMB_NTSTATUS_PNP_INVALID_ID: u32 = 0xc0040038; +pub const SMB_NTSTATUS_IO_REISSUE_AS_CACHED: u32 = 0xc0040039; +pub const SMB_NTSTATUS_CTX_WINSTATION_NAME_INVALID: u32 = 0xc00a0001; +pub const SMB_NTSTATUS_CTX_INVALID_PD: u32 = 0xc00a0002; +pub const SMB_NTSTATUS_CTX_PD_NOT_FOUND: u32 = 0xc00a0003; +pub const SMB_NTSTATUS_CTX_CLOSE_PENDING: u32 = 0xc00a0006; +pub const SMB_NTSTATUS_CTX_NO_OUTBUF: u32 = 0xc00a0007; +pub const SMB_NTSTATUS_CTX_MODEM_INF_NOT_FOUND: u32 = 0xc00a0008; +pub const SMB_NTSTATUS_CTX_INVALID_MODEMNAME: u32 = 0xc00a0009; +pub const SMB_NTSTATUS_CTX_RESPONSE_ERROR: u32 = 0xc00a000a; +pub const SMB_NTSTATUS_CTX_MODEM_RESPONSE_TIMEOUT: u32 = 0xc00a000b; +pub const SMB_NTSTATUS_CTX_MODEM_RESPONSE_NO_CARRIER: u32 = 0xc00a000c; +pub const SMB_NTSTATUS_CTX_MODEM_RESPONSE_NO_DIALTONE: u32 = 0xc00a000d; +pub const SMB_NTSTATUS_CTX_MODEM_RESPONSE_BUSY: u32 = 0xc00a000e; +pub const SMB_NTSTATUS_CTX_MODEM_RESPONSE_VOICE: u32 = 0xc00a000f; +pub const SMB_NTSTATUS_CTX_TD_ERROR: u32 = 0xc00a0010; +pub const SMB_NTSTATUS_CTX_LICENSE_CLIENT_INVALID: u32 = 0xc00a0012; +pub const SMB_NTSTATUS_CTX_LICENSE_NOT_AVAILABLE: u32 = 0xc00a0013; +pub const SMB_NTSTATUS_CTX_LICENSE_EXPIRED: u32 = 0xc00a0014; +pub const SMB_NTSTATUS_CTX_WINSTATION_NOT_FOUND: u32 = 0xc00a0015; +pub const SMB_NTSTATUS_CTX_WINSTATION_NAME_COLLISION: u32 = 0xc00a0016; +pub const SMB_NTSTATUS_CTX_WINSTATION_BUSY: u32 = 0xc00a0017; +pub const SMB_NTSTATUS_CTX_BAD_VIDEO_MODE: u32 = 0xc00a0018; +pub const SMB_NTSTATUS_CTX_GRAPHICS_INVALID: u32 = 0xc00a0022; +pub const SMB_NTSTATUS_CTX_NOT_CONSOLE: u32 = 0xc00a0024; +pub const SMB_NTSTATUS_CTX_CLIENT_QUERY_TIMEOUT: u32 = 0xc00a0026; +pub const SMB_NTSTATUS_CTX_CONSOLE_DISCONNECT: u32 = 0xc00a0027; +pub const SMB_NTSTATUS_CTX_CONSOLE_CONNECT: u32 = 0xc00a0028; +pub const SMB_NTSTATUS_CTX_SHADOW_DENIED: u32 = 0xc00a002a; +pub const SMB_NTSTATUS_CTX_WINSTATION_ACCESS_DENIED: u32 = 0xc00a002b; +pub const SMB_NTSTATUS_CTX_INVALID_WD: u32 = 0xc00a002e; +pub const SMB_NTSTATUS_CTX_WD_NOT_FOUND: u32 = 0xc00a002f; +pub const SMB_NTSTATUS_CTX_SHADOW_INVALID: u32 = 0xc00a0030; +pub const SMB_NTSTATUS_CTX_SHADOW_DISABLED: u32 = 0xc00a0031; +pub const SMB_NTSTATUS_RDP_PROTOCOL_ERROR: u32 = 0xc00a0032; +pub const SMB_NTSTATUS_CTX_CLIENT_LICENSE_NOT_SET: u32 = 0xc00a0033; +pub const SMB_NTSTATUS_CTX_CLIENT_LICENSE_IN_USE: u32 = 0xc00a0034; +pub const SMB_NTSTATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE: u32 = 0xc00a0035; +pub const SMB_NTSTATUS_CTX_SHADOW_NOT_RUNNING: u32 = 0xc00a0036; +pub const SMB_NTSTATUS_CTX_LOGON_DISABLED: u32 = 0xc00a0037; +pub const SMB_NTSTATUS_CTX_SECURITY_LAYER_ERROR: u32 = 0xc00a0038; +pub const SMB_NTSTATUS_TS_INCOMPATIBLE_SESSIONS: u32 = 0xc00a0039; +pub const SMB_NTSTATUS_MUI_FILE_NOT_FOUND: u32 = 0xc00b0001; +pub const SMB_NTSTATUS_MUI_INVALID_FILE: u32 = 0xc00b0002; +pub const SMB_NTSTATUS_MUI_INVALID_RC_CONFIG: u32 = 0xc00b0003; +pub const SMB_NTSTATUS_MUI_INVALID_LOCALE_NAME: u32 = 0xc00b0004; +pub const SMB_NTSTATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME: u32 = 0xc00b0005; +pub const SMB_NTSTATUS_MUI_FILE_NOT_LOADED: u32 = 0xc00b0006; +pub const SMB_NTSTATUS_RESOURCE_ENUM_USER_STOP: u32 = 0xc00b0007; +pub const SMB_NTSTATUS_CLUSTER_INVALID_NODE: u32 = 0xc0130001; +pub const SMB_NTSTATUS_CLUSTER_NODE_EXISTS: u32 = 0xc0130002; +pub const SMB_NTSTATUS_CLUSTER_JOIN_IN_PROGRESS: u32 = 0xc0130003; +pub const SMB_NTSTATUS_CLUSTER_NODE_NOT_FOUND: u32 = 0xc0130004; +pub const SMB_NTSTATUS_CLUSTER_LOCAL_NODE_NOT_FOUND: u32 = 0xc0130005; +pub const SMB_NTSTATUS_CLUSTER_NETWORK_EXISTS: u32 = 0xc0130006; +pub const SMB_NTSTATUS_CLUSTER_NETWORK_NOT_FOUND: u32 = 0xc0130007; +pub const SMB_NTSTATUS_CLUSTER_NETINTERFACE_EXISTS: u32 = 0xc0130008; +pub const SMB_NTSTATUS_CLUSTER_NETINTERFACE_NOT_FOUND: u32 = 0xc0130009; +pub const SMB_NTSTATUS_CLUSTER_INVALID_REQUEST: u32 = 0xc013000a; +pub const SMB_NTSTATUS_CLUSTER_INVALID_NETWORK_PROVIDER: u32 = 0xc013000b; +pub const SMB_NTSTATUS_CLUSTER_NODE_DOWN: u32 = 0xc013000c; +pub const SMB_NTSTATUS_CLUSTER_NODE_UNREACHABLE: u32 = 0xc013000d; +pub const SMB_NTSTATUS_CLUSTER_NODE_NOT_MEMBER: u32 = 0xc013000e; +pub const SMB_NTSTATUS_CLUSTER_JOIN_NOT_IN_PROGRESS: u32 = 0xc013000f; +pub const SMB_NTSTATUS_CLUSTER_INVALID_NETWORK: u32 = 0xc0130010; +pub const SMB_NTSTATUS_CLUSTER_NO_NET_ADAPTERS: u32 = 0xc0130011; +pub const SMB_NTSTATUS_CLUSTER_NODE_UP: u32 = 0xc0130012; +pub const SMB_NTSTATUS_CLUSTER_NODE_PAUSED: u32 = 0xc0130013; +pub const SMB_NTSTATUS_CLUSTER_NODE_NOT_PAUSED: u32 = 0xc0130014; +pub const SMB_NTSTATUS_CLUSTER_NO_SECURITY_CONTEXT: u32 = 0xc0130015; +pub const SMB_NTSTATUS_CLUSTER_NETWORK_NOT_INTERNAL: u32 = 0xc0130016; +pub const SMB_NTSTATUS_CLUSTER_POISONED: u32 = 0xc0130017; +pub const SMB_NTSTATUS_ACPI_INVALID_OPCODE: u32 = 0xc0140001; +pub const SMB_NTSTATUS_ACPI_STACK_OVERFLOW: u32 = 0xc0140002; +pub const SMB_NTSTATUS_ACPI_ASSERT_FAILED: u32 = 0xc0140003; +pub const SMB_NTSTATUS_ACPI_INVALID_INDEX: u32 = 0xc0140004; +pub const SMB_NTSTATUS_ACPI_INVALID_ARGUMENT: u32 = 0xc0140005; +pub const SMB_NTSTATUS_ACPI_FATAL: u32 = 0xc0140006; +pub const SMB_NTSTATUS_ACPI_INVALID_SUPERNAME: u32 = 0xc0140007; +pub const SMB_NTSTATUS_ACPI_INVALID_ARGTYPE: u32 = 0xc0140008; +pub const SMB_NTSTATUS_ACPI_INVALID_OBJTYPE: u32 = 0xc0140009; +pub const SMB_NTSTATUS_ACPI_INVALID_TARGETTYPE: u32 = 0xc014000a; +pub const SMB_NTSTATUS_ACPI_INCORRECT_ARGUMENT_COUNT: u32 = 0xc014000b; +pub const SMB_NTSTATUS_ACPI_ADDRESS_NOT_MAPPED: u32 = 0xc014000c; +pub const SMB_NTSTATUS_ACPI_INVALID_EVENTTYPE: u32 = 0xc014000d; +pub const SMB_NTSTATUS_ACPI_HANDLER_COLLISION: u32 = 0xc014000e; +pub const SMB_NTSTATUS_ACPI_INVALID_DATA: u32 = 0xc014000f; +pub const SMB_NTSTATUS_ACPI_INVALID_REGION: u32 = 0xc0140010; +pub const SMB_NTSTATUS_ACPI_INVALID_ACCESS_SIZE: u32 = 0xc0140011; +pub const SMB_NTSTATUS_ACPI_ACQUIRE_GLOBAL_LOCK: u32 = 0xc0140012; +pub const SMB_NTSTATUS_ACPI_ALREADY_INITIALIZED: u32 = 0xc0140013; +pub const SMB_NTSTATUS_ACPI_NOT_INITIALIZED: u32 = 0xc0140014; +pub const SMB_NTSTATUS_ACPI_INVALID_MUTEX_LEVEL: u32 = 0xc0140015; +pub const SMB_NTSTATUS_ACPI_MUTEX_NOT_OWNED: u32 = 0xc0140016; +pub const SMB_NTSTATUS_ACPI_MUTEX_NOT_OWNER: u32 = 0xc0140017; +pub const SMB_NTSTATUS_ACPI_RS_ACCESS: u32 = 0xc0140018; +pub const SMB_NTSTATUS_ACPI_INVALID_TABLE: u32 = 0xc0140019; +pub const SMB_NTSTATUS_ACPI_REG_HANDLER_FAILED: u32 = 0xc0140020; +pub const SMB_NTSTATUS_ACPI_POWER_REQUEST_FAILED: u32 = 0xc0140021; +pub const SMB_NTSTATUS_SXS_SECTION_NOT_FOUND: u32 = 0xc0150001; +pub const SMB_NTSTATUS_SXS_CANT_GEN_ACTCTX: u32 = 0xc0150002; +pub const SMB_NTSTATUS_SXS_INVALID_ACTCTXDATA_FORMAT: u32 = 0xc0150003; +pub const SMB_NTSTATUS_SXS_ASSEMBLY_NOT_FOUND: u32 = 0xc0150004; +pub const SMB_NTSTATUS_SXS_MANIFEST_FORMAT_ERROR: u32 = 0xc0150005; +pub const SMB_NTSTATUS_SXS_MANIFEST_PARSE_ERROR: u32 = 0xc0150006; +pub const SMB_NTSTATUS_SXS_ACTIVATION_CONTEXT_DISABLED: u32 = 0xc0150007; +pub const SMB_NTSTATUS_SXS_KEY_NOT_FOUND: u32 = 0xc0150008; +pub const SMB_NTSTATUS_SXS_VERSION_CONFLICT: u32 = 0xc0150009; +pub const SMB_NTSTATUS_SXS_WRONG_SECTION_TYPE: u32 = 0xc015000a; +pub const SMB_NTSTATUS_SXS_THREAD_QUERIES_DISABLED: u32 = 0xc015000b; +pub const SMB_NTSTATUS_SXS_ASSEMBLY_MISSING: u32 = 0xc015000c; +pub const SMB_NTSTATUS_SXS_PROCESS_DEFAULT_ALREADY_SET: u32 = 0xc015000e; +pub const SMB_NTSTATUS_SXS_EARLY_DEACTIVATION: u32 = 0xc015000f; +pub const SMB_NTSTATUS_SXS_INVALID_DEACTIVATION: u32 = 0xc0150010; +pub const SMB_NTSTATUS_SXS_MULTIPLE_DEACTIVATION: u32 = 0xc0150011; +pub const SMB_NTSTATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY: u32 = 0xc0150012; +pub const SMB_NTSTATUS_SXS_PROCESS_TERMINATION_REQUESTED: u32 = 0xc0150013; +pub const SMB_NTSTATUS_SXS_CORRUPT_ACTIVATION_STACK: u32 = 0xc0150014; +pub const SMB_NTSTATUS_SXS_CORRUPTION: u32 = 0xc0150015; +pub const SMB_NTSTATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE: u32 = 0xc0150016; +pub const SMB_NTSTATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME: u32 = 0xc0150017; +pub const SMB_NTSTATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE: u32 = 0xc0150018; +pub const SMB_NTSTATUS_SXS_IDENTITY_PARSE_ERROR: u32 = 0xc0150019; +pub const SMB_NTSTATUS_SXS_COMPONENT_STORE_CORRUPT: u32 = 0xc015001a; +pub const SMB_NTSTATUS_SXS_FILE_HASH_MISMATCH: u32 = 0xc015001b; +pub const SMB_NTSTATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT: u32 = 0xc015001c; +pub const SMB_NTSTATUS_SXS_IDENTITIES_DIFFERENT: u32 = 0xc015001d; +pub const SMB_NTSTATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT: u32 = 0xc015001e; +pub const SMB_NTSTATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY: u32 = 0xc015001f; +pub const SMB_NTSTATUS_ADVANCED_INSTALLER_FAILED: u32 = 0xc0150020; +pub const SMB_NTSTATUS_XML_ENCODING_MISMATCH: u32 = 0xc0150021; +pub const SMB_NTSTATUS_SXS_MANIFEST_TOO_BIG: u32 = 0xc0150022; +pub const SMB_NTSTATUS_SXS_SETTING_NOT_REGISTERED: u32 = 0xc0150023; +pub const SMB_NTSTATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE: u32 = 0xc0150024; +pub const SMB_NTSTATUS_SMI_PRIMITIVE_INSTALLER_FAILED: u32 = 0xc0150025; +pub const SMB_NTSTATUS_GENERIC_COMMAND_FAILED: u32 = 0xc0150026; +pub const SMB_NTSTATUS_SXS_FILE_HASH_MISSING: u32 = 0xc0150027; +pub const SMB_NTSTATUS_TRANSACTIONAL_CONFLICT: u32 = 0xc0190001; +pub const SMB_NTSTATUS_INVALID_TRANSACTION: u32 = 0xc0190002; +pub const SMB_NTSTATUS_TRANSACTION_NOT_ACTIVE: u32 = 0xc0190003; +pub const SMB_NTSTATUS_TM_INITIALIZATION_FAILED: u32 = 0xc0190004; +pub const SMB_NTSTATUS_RM_NOT_ACTIVE: u32 = 0xc0190005; +pub const SMB_NTSTATUS_RM_METADATA_CORRUPT: u32 = 0xc0190006; +pub const SMB_NTSTATUS_TRANSACTION_NOT_JOINED: u32 = 0xc0190007; +pub const SMB_NTSTATUS_DIRECTORY_NOT_RM: u32 = 0xc0190008; +pub const SMB_NTSTATUS_TRANSACTIONS_UNSUPPORTED_REMOTE: u32 = 0xc019000a; +pub const SMB_NTSTATUS_LOG_RESIZE_INVALID_SIZE: u32 = 0xc019000b; +pub const SMB_NTSTATUS_REMOTE_FILE_VERSION_MISMATCH: u32 = 0xc019000c; +pub const SMB_NTSTATUS_CRM_PROTOCOL_ALREADY_EXISTS: u32 = 0xc019000f; +pub const SMB_NTSTATUS_TRANSACTION_PROPAGATION_FAILED: u32 = 0xc0190010; +pub const SMB_NTSTATUS_CRM_PROTOCOL_NOT_FOUND: u32 = 0xc0190011; +pub const SMB_NTSTATUS_TRANSACTION_SUPERIOR_EXISTS: u32 = 0xc0190012; +pub const SMB_NTSTATUS_TRANSACTION_REQUEST_NOT_VALID: u32 = 0xc0190013; +pub const SMB_NTSTATUS_TRANSACTION_NOT_REQUESTED: u32 = 0xc0190014; +pub const SMB_NTSTATUS_TRANSACTION_ALREADY_ABORTED: u32 = 0xc0190015; +pub const SMB_NTSTATUS_TRANSACTION_ALREADY_COMMITTED: u32 = 0xc0190016; +pub const SMB_NTSTATUS_TRANSACTION_INVALID_MARSHALL_BUFFER: u32 = 0xc0190017; +pub const SMB_NTSTATUS_CURRENT_TRANSACTION_NOT_VALID: u32 = 0xc0190018; +pub const SMB_NTSTATUS_LOG_GROWTH_FAILED: u32 = 0xc0190019; +pub const SMB_NTSTATUS_OBJECT_NO_LONGER_EXISTS: u32 = 0xc0190021; +pub const SMB_NTSTATUS_STREAM_MINIVERSION_NOT_FOUND: u32 = 0xc0190022; +pub const SMB_NTSTATUS_STREAM_MINIVERSION_NOT_VALID: u32 = 0xc0190023; +pub const SMB_NTSTATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION: u32 = 0xc0190024; +pub const SMB_NTSTATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT: u32 = 0xc0190025; +pub const SMB_NTSTATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS: u32 = 0xc0190026; +pub const SMB_NTSTATUS_HANDLE_NO_LONGER_VALID: u32 = 0xc0190028; +pub const SMB_NTSTATUS_LOG_CORRUPTION_DETECTED: u32 = 0xc0190030; +pub const SMB_NTSTATUS_RM_DISCONNECTED: u32 = 0xc0190032; +pub const SMB_NTSTATUS_ENLISTMENT_NOT_SUPERIOR: u32 = 0xc0190033; +pub const SMB_NTSTATUS_FILE_IDENTITY_NOT_PERSISTENT: u32 = 0xc0190036; +pub const SMB_NTSTATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY: u32 = 0xc0190037; +pub const SMB_NTSTATUS_CANT_CROSS_RM_BOUNDARY: u32 = 0xc0190038; +pub const SMB_NTSTATUS_TXF_DIR_NOT_EMPTY: u32 = 0xc0190039; +pub const SMB_NTSTATUS_INDOUBT_TRANSACTIONS_EXIST: u32 = 0xc019003a; +pub const SMB_NTSTATUS_TM_VOLATILE: u32 = 0xc019003b; +pub const SMB_NTSTATUS_ROLLBACK_TIMER_EXPIRED: u32 = 0xc019003c; +pub const SMB_NTSTATUS_TXF_ATTRIBUTE_CORRUPT: u32 = 0xc019003d; +pub const SMB_NTSTATUS_EFS_NOT_ALLOWED_IN_TRANSACTION: u32 = 0xc019003e; +pub const SMB_NTSTATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED: u32 = 0xc019003f; +pub const SMB_NTSTATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE: u32 = 0xc0190040; +pub const SMB_NTSTATUS_TRANSACTION_REQUIRED_PROMOTION: u32 = 0xc0190043; +pub const SMB_NTSTATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION: u32 = 0xc0190044; +pub const SMB_NTSTATUS_TRANSACTIONS_NOT_FROZEN: u32 = 0xc0190045; +pub const SMB_NTSTATUS_TRANSACTION_FREEZE_IN_PROGRESS: u32 = 0xc0190046; +pub const SMB_NTSTATUS_NOT_SNAPSHOT_VOLUME: u32 = 0xc0190047; +pub const SMB_NTSTATUS_NO_SAVEPOINT_WITH_OPEN_FILES: u32 = 0xc0190048; +pub const SMB_NTSTATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION: u32 = 0xc0190049; +pub const SMB_NTSTATUS_TM_IDENTITY_MISMATCH: u32 = 0xc019004a; +pub const SMB_NTSTATUS_FLOATED_SECTION: u32 = 0xc019004b; +pub const SMB_NTSTATUS_CANNOT_ACCEPT_TRANSACTED_WORK: u32 = 0xc019004c; +pub const SMB_NTSTATUS_CANNOT_ABORT_TRANSACTIONS: u32 = 0xc019004d; +pub const SMB_NTSTATUS_TRANSACTION_NOT_FOUND: u32 = 0xc019004e; +pub const SMB_NTSTATUS_RESOURCEMANAGER_NOT_FOUND: u32 = 0xc019004f; +pub const SMB_NTSTATUS_ENLISTMENT_NOT_FOUND: u32 = 0xc0190050; +pub const SMB_NTSTATUS_TRANSACTIONMANAGER_NOT_FOUND: u32 = 0xc0190051; +pub const SMB_NTSTATUS_TRANSACTIONMANAGER_NOT_ONLINE: u32 = 0xc0190052; +pub const SMB_NTSTATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION: u32 = 0xc0190053; +pub const SMB_NTSTATUS_TRANSACTION_NOT_ROOT: u32 = 0xc0190054; +pub const SMB_NTSTATUS_TRANSACTION_OBJECT_EXPIRED: u32 = 0xc0190055; +pub const SMB_NTSTATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION: u32 = 0xc0190056; +pub const SMB_NTSTATUS_TRANSACTION_RESPONSE_NOT_ENLISTED: u32 = 0xc0190057; +pub const SMB_NTSTATUS_TRANSACTION_RECORD_TOO_LONG: u32 = 0xc0190058; +pub const SMB_NTSTATUS_NO_LINK_TRACKING_IN_TRANSACTION: u32 = 0xc0190059; +pub const SMB_NTSTATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION: u32 = 0xc019005a; +pub const SMB_NTSTATUS_TRANSACTION_INTEGRITY_VIOLATED: u32 = 0xc019005b; +pub const SMB_NTSTATUS_EXPIRED_HANDLE: u32 = 0xc0190060; +pub const SMB_NTSTATUS_TRANSACTION_NOT_ENLISTED: u32 = 0xc0190061; +pub const SMB_NTSTATUS_LOG_SECTOR_INVALID: u32 = 0xc01a0001; +pub const SMB_NTSTATUS_LOG_SECTOR_PARITY_INVALID: u32 = 0xc01a0002; +pub const SMB_NTSTATUS_LOG_SECTOR_REMAPPED: u32 = 0xc01a0003; +pub const SMB_NTSTATUS_LOG_BLOCK_INCOMPLETE: u32 = 0xc01a0004; +pub const SMB_NTSTATUS_LOG_INVALID_RANGE: u32 = 0xc01a0005; +pub const SMB_NTSTATUS_LOG_BLOCKS_EXHAUSTED: u32 = 0xc01a0006; +pub const SMB_NTSTATUS_LOG_READ_CONTEXT_INVALID: u32 = 0xc01a0007; +pub const SMB_NTSTATUS_LOG_RESTART_INVALID: u32 = 0xc01a0008; +pub const SMB_NTSTATUS_LOG_BLOCK_VERSION: u32 = 0xc01a0009; +pub const SMB_NTSTATUS_LOG_BLOCK_INVALID: u32 = 0xc01a000a; +pub const SMB_NTSTATUS_LOG_READ_MODE_INVALID: u32 = 0xc01a000b; +pub const SMB_NTSTATUS_LOG_METADATA_CORRUPT: u32 = 0xc01a000d; +pub const SMB_NTSTATUS_LOG_METADATA_INVALID: u32 = 0xc01a000e; +pub const SMB_NTSTATUS_LOG_METADATA_INCONSISTENT: u32 = 0xc01a000f; +pub const SMB_NTSTATUS_LOG_RESERVATION_INVALID: u32 = 0xc01a0010; +pub const SMB_NTSTATUS_LOG_CANT_DELETE: u32 = 0xc01a0011; +pub const SMB_NTSTATUS_LOG_CONTAINER_LIMIT_EXCEEDED: u32 = 0xc01a0012; +pub const SMB_NTSTATUS_LOG_START_OF_LOG: u32 = 0xc01a0013; +pub const SMB_NTSTATUS_LOG_POLICY_ALREADY_INSTALLED: u32 = 0xc01a0014; +pub const SMB_NTSTATUS_LOG_POLICY_NOT_INSTALLED: u32 = 0xc01a0015; +pub const SMB_NTSTATUS_LOG_POLICY_INVALID: u32 = 0xc01a0016; +pub const SMB_NTSTATUS_LOG_POLICY_CONFLICT: u32 = 0xc01a0017; +pub const SMB_NTSTATUS_LOG_PINNED_ARCHIVE_TAIL: u32 = 0xc01a0018; +pub const SMB_NTSTATUS_LOG_RECORD_NONEXISTENT: u32 = 0xc01a0019; +pub const SMB_NTSTATUS_LOG_RECORDS_RESERVED_INVALID: u32 = 0xc01a001a; +pub const SMB_NTSTATUS_LOG_SPACE_RESERVED_INVALID: u32 = 0xc01a001b; +pub const SMB_NTSTATUS_LOG_TAIL_INVALID: u32 = 0xc01a001c; +pub const SMB_NTSTATUS_LOG_FULL: u32 = 0xc01a001d; +pub const SMB_NTSTATUS_LOG_MULTIPLEXED: u32 = 0xc01a001e; +pub const SMB_NTSTATUS_LOG_DEDICATED: u32 = 0xc01a001f; +pub const SMB_NTSTATUS_LOG_ARCHIVE_NOT_IN_PROGRESS: u32 = 0xc01a0020; +pub const SMB_NTSTATUS_LOG_ARCHIVE_IN_PROGRESS: u32 = 0xc01a0021; +pub const SMB_NTSTATUS_LOG_EPHEMERAL: u32 = 0xc01a0022; +pub const SMB_NTSTATUS_LOG_NOT_ENOUGH_CONTAINERS: u32 = 0xc01a0023; +pub const SMB_NTSTATUS_LOG_CLIENT_ALREADY_REGISTERED: u32 = 0xc01a0024; +pub const SMB_NTSTATUS_LOG_CLIENT_NOT_REGISTERED: u32 = 0xc01a0025; +pub const SMB_NTSTATUS_LOG_FULL_HANDLER_IN_PROGRESS: u32 = 0xc01a0026; +pub const SMB_NTSTATUS_LOG_CONTAINER_READ_FAILED: u32 = 0xc01a0027; +pub const SMB_NTSTATUS_LOG_CONTAINER_WRITE_FAILED: u32 = 0xc01a0028; +pub const SMB_NTSTATUS_LOG_CONTAINER_OPEN_FAILED: u32 = 0xc01a0029; +pub const SMB_NTSTATUS_LOG_CONTAINER_STATE_INVALID: u32 = 0xc01a002a; +pub const SMB_NTSTATUS_LOG_STATE_INVALID: u32 = 0xc01a002b; +pub const SMB_NTSTATUS_LOG_PINNED: u32 = 0xc01a002c; +pub const SMB_NTSTATUS_LOG_METADATA_FLUSH_FAILED: u32 = 0xc01a002d; +pub const SMB_NTSTATUS_LOG_INCONSISTENT_SECURITY: u32 = 0xc01a002e; +pub const SMB_NTSTATUS_LOG_APPENDED_FLUSH_FAILED: u32 = 0xc01a002f; +pub const SMB_NTSTATUS_LOG_PINNED_RESERVATION: u32 = 0xc01a0030; +pub const SMB_NTSTATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD: u32 = 0xc01b00ea; +pub const SMB_NTSTATUS_FLT_NO_HANDLER_DEFINED: u32 = 0xc01c0001; +pub const SMB_NTSTATUS_FLT_CONTEXT_ALREADY_DEFINED: u32 = 0xc01c0002; +pub const SMB_NTSTATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST: u32 = 0xc01c0003; +pub const SMB_NTSTATUS_FLT_DISALLOW_FAST_IO: u32 = 0xc01c0004; +pub const SMB_NTSTATUS_FLT_INVALID_NAME_REQUEST: u32 = 0xc01c0005; +pub const SMB_NTSTATUS_FLT_NOT_SAFE_TO_POST_OPERATION: u32 = 0xc01c0006; +pub const SMB_NTSTATUS_FLT_NOT_INITIALIZED: u32 = 0xc01c0007; +pub const SMB_NTSTATUS_FLT_FILTER_NOT_READY: u32 = 0xc01c0008; +pub const SMB_NTSTATUS_FLT_POST_OPERATION_CLEANUP: u32 = 0xc01c0009; +pub const SMB_NTSTATUS_FLT_INTERNAL_ERROR: u32 = 0xc01c000a; +pub const SMB_NTSTATUS_FLT_DELETING_OBJECT: u32 = 0xc01c000b; +pub const SMB_NTSTATUS_FLT_MUST_BE_NONPAGED_POOL: u32 = 0xc01c000c; +pub const SMB_NTSTATUS_FLT_DUPLICATE_ENTRY: u32 = 0xc01c000d; +pub const SMB_NTSTATUS_FLT_CBDQ_DISABLED: u32 = 0xc01c000e; +pub const SMB_NTSTATUS_FLT_DO_NOT_ATTACH: u32 = 0xc01c000f; +pub const SMB_NTSTATUS_FLT_DO_NOT_DETACH: u32 = 0xc01c0010; +pub const SMB_NTSTATUS_FLT_INSTANCE_ALTITUDE_COLLISION: u32 = 0xc01c0011; +pub const SMB_NTSTATUS_FLT_INSTANCE_NAME_COLLISION: u32 = 0xc01c0012; +pub const SMB_NTSTATUS_FLT_FILTER_NOT_FOUND: u32 = 0xc01c0013; +pub const SMB_NTSTATUS_FLT_VOLUME_NOT_FOUND: u32 = 0xc01c0014; +pub const SMB_NTSTATUS_FLT_INSTANCE_NOT_FOUND: u32 = 0xc01c0015; +pub const SMB_NTSTATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND: u32 = 0xc01c0016; +pub const SMB_NTSTATUS_FLT_INVALID_CONTEXT_REGISTRATION: u32 = 0xc01c0017; +pub const SMB_NTSTATUS_FLT_NAME_CACHE_MISS: u32 = 0xc01c0018; +pub const SMB_NTSTATUS_FLT_NO_DEVICE_OBJECT: u32 = 0xc01c0019; +pub const SMB_NTSTATUS_FLT_VOLUME_ALREADY_MOUNTED: u32 = 0xc01c001a; +pub const SMB_NTSTATUS_FLT_ALREADY_ENLISTED: u32 = 0xc01c001b; +pub const SMB_NTSTATUS_FLT_CONTEXT_ALREADY_LINKED: u32 = 0xc01c001c; +pub const SMB_NTSTATUS_FLT_NO_WAITER_FOR_REPLY: u32 = 0xc01c0020; +pub const SMB_NTSTATUS_MONITOR_NO_DESCRIPTOR: u32 = 0xc01d0001; +pub const SMB_NTSTATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT: u32 = 0xc01d0002; +pub const SMB_NTSTATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM: u32 = 0xc01d0003; +pub const SMB_NTSTATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK: u32 = 0xc01d0004; +pub const SMB_NTSTATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED: u32 = 0xc01d0005; +pub const SMB_NTSTATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK: u32 = 0xc01d0006; +pub const SMB_NTSTATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK: u32 = 0xc01d0007; +pub const SMB_NTSTATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA: u32 = 0xc01d0008; +pub const SMB_NTSTATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK: u32 = 0xc01d0009; +pub const SMB_NTSTATUS_MONITOR_INVALID_MANUFACTURE_DATE: u32 = 0xc01d000a; +pub const SMB_NTSTATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER: u32 = 0xc01e0000; +pub const SMB_NTSTATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER: u32 = 0xc01e0001; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER: u32 = 0xc01e0002; +pub const SMB_NTSTATUS_GRAPHICS_ADAPTER_WAS_RESET: u32 = 0xc01e0003; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_DRIVER_MODEL: u32 = 0xc01e0004; +pub const SMB_NTSTATUS_GRAPHICS_PRESENT_MODE_CHANGED: u32 = 0xc01e0005; +pub const SMB_NTSTATUS_GRAPHICS_PRESENT_OCCLUDED: u32 = 0xc01e0006; +pub const SMB_NTSTATUS_GRAPHICS_PRESENT_DENIED: u32 = 0xc01e0007; +pub const SMB_NTSTATUS_GRAPHICS_CANNOTCOLORCONVERT: u32 = 0xc01e0008; +pub const SMB_NTSTATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED: u32 = 0xc01e000b; +pub const SMB_NTSTATUS_GRAPHICS_PRESENT_UNOCCLUDED: u32 = 0xc01e000c; +pub const SMB_NTSTATUS_GRAPHICS_NO_VIDEO_MEMORY: u32 = 0xc01e0100; +pub const SMB_NTSTATUS_GRAPHICS_CANT_LOCK_MEMORY: u32 = 0xc01e0101; +pub const SMB_NTSTATUS_GRAPHICS_ALLOCATION_BUSY: u32 = 0xc01e0102; +pub const SMB_NTSTATUS_GRAPHICS_TOO_MANY_REFERENCES: u32 = 0xc01e0103; +pub const SMB_NTSTATUS_GRAPHICS_TRY_AGAIN_LATER: u32 = 0xc01e0104; +pub const SMB_NTSTATUS_GRAPHICS_TRY_AGAIN_NOW: u32 = 0xc01e0105; +pub const SMB_NTSTATUS_GRAPHICS_ALLOCATION_INVALID: u32 = 0xc01e0106; +pub const SMB_NTSTATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE: u32 = 0xc01e0107; +pub const SMB_NTSTATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED: u32 = 0xc01e0108; +pub const SMB_NTSTATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION: u32 = 0xc01e0109; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_USAGE: u32 = 0xc01e0110; +pub const SMB_NTSTATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION: u32 = 0xc01e0111; +pub const SMB_NTSTATUS_GRAPHICS_ALLOCATION_CLOSED: u32 = 0xc01e0112; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE: u32 = 0xc01e0113; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE: u32 = 0xc01e0114; +pub const SMB_NTSTATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE: u32 = 0xc01e0115; +pub const SMB_NTSTATUS_GRAPHICS_ALLOCATION_CONTENT_LOST: u32 = 0xc01e0116; +pub const SMB_NTSTATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE: u32 = 0xc01e0200; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY: u32 = 0xc01e0300; +pub const SMB_NTSTATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED: u32 = 0xc01e0301; +pub const SMB_NTSTATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED: u32 = 0xc01e0302; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN: u32 = 0xc01e0303; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE: u32 = 0xc01e0304; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET: u32 = 0xc01e0305; +pub const SMB_NTSTATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED: u32 = 0xc01e0306; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET: u32 = 0xc01e0308; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET: u32 = 0xc01e0309; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_FREQUENCY: u32 = 0xc01e030a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_ACTIVE_REGION: u32 = 0xc01e030b; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_TOTAL_REGION: u32 = 0xc01e030c; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE: u32 = 0xc01e0310; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE: u32 = 0xc01e0311; +pub const SMB_NTSTATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET: u32 = 0xc01e0312; +pub const SMB_NTSTATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY: u32 = 0xc01e0313; +pub const SMB_NTSTATUS_GRAPHICS_MODE_ALREADY_IN_MODESET: u32 = 0xc01e0314; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET: u32 = 0xc01e0315; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET: u32 = 0xc01e0316; +pub const SMB_NTSTATUS_GRAPHICS_SOURCE_ALREADY_IN_SET: u32 = 0xc01e0317; +pub const SMB_NTSTATUS_GRAPHICS_TARGET_ALREADY_IN_SET: u32 = 0xc01e0318; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH: u32 = 0xc01e0319; +pub const SMB_NTSTATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY: u32 = 0xc01e031a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET: u32 = 0xc01e031b; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE: u32 = 0xc01e031c; +pub const SMB_NTSTATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET: u32 = 0xc01e031d; +pub const SMB_NTSTATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET: u32 = 0xc01e031f; +pub const SMB_NTSTATUS_GRAPHICS_STALE_MODESET: u32 = 0xc01e0320; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET: u32 = 0xc01e0321; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE: u32 = 0xc01e0322; +pub const SMB_NTSTATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN: u32 = 0xc01e0323; +pub const SMB_NTSTATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE: u32 = 0xc01e0324; +pub const SMB_NTSTATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION: u32 = 0xc01e0325; +pub const SMB_NTSTATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES: u32 = 0xc01e0326; +pub const SMB_NTSTATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY: u32 = 0xc01e0327; +pub const SMB_NTSTATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE: u32 = 0xc01e0328; +pub const SMB_NTSTATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET: u32 = 0xc01e0329; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET: u32 = 0xc01e032a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR: u32 = 0xc01e032b; +pub const SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET: u32 = 0xc01e032c; +pub const SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET: u32 = 0xc01e032d; +pub const SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE: u32 = 0xc01e032e; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE: u32 = 0xc01e032f; +pub const SMB_NTSTATUS_GRAPHICS_RESOURCES_NOT_RELATED: u32 = 0xc01e0330; +pub const SMB_NTSTATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE: u32 = 0xc01e0331; +pub const SMB_NTSTATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE: u32 = 0xc01e0332; +pub const SMB_NTSTATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET: u32 = 0xc01e0333; +pub const SMB_NTSTATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER: u32 = 0xc01e0334; +pub const SMB_NTSTATUS_GRAPHICS_NO_VIDPNMGR: u32 = 0xc01e0335; +pub const SMB_NTSTATUS_GRAPHICS_NO_ACTIVE_VIDPN: u32 = 0xc01e0336; +pub const SMB_NTSTATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY: u32 = 0xc01e0337; +pub const SMB_NTSTATUS_GRAPHICS_MONITOR_NOT_CONNECTED: u32 = 0xc01e0338; +pub const SMB_NTSTATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY: u32 = 0xc01e0339; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE: u32 = 0xc01e033a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE: u32 = 0xc01e033b; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_STRIDE: u32 = 0xc01e033c; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PIXELFORMAT: u32 = 0xc01e033d; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_COLORBASIS: u32 = 0xc01e033e; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE: u32 = 0xc01e033f; +pub const SMB_NTSTATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY: u32 = 0xc01e0340; +pub const SMB_NTSTATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT: u32 = 0xc01e0341; +pub const SMB_NTSTATUS_GRAPHICS_VIDPN_SOURCE_IN_USE: u32 = 0xc01e0342; +pub const SMB_NTSTATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN: u32 = 0xc01e0343; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL: u32 = 0xc01e0344; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION: u32 = 0xc01e0345; +pub const SMB_NTSTATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED: u32 = 0xc01e0346; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_GAMMA_RAMP: u32 = 0xc01e0347; +pub const SMB_NTSTATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED: u32 = 0xc01e0348; +pub const SMB_NTSTATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED: u32 = 0xc01e0349; +pub const SMB_NTSTATUS_GRAPHICS_MODE_NOT_IN_MODESET: u32 = 0xc01e034a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON: u32 = 0xc01e034d; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE: u32 = 0xc01e034e; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE: u32 = 0xc01e034f; +pub const SMB_NTSTATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS: u32 = 0xc01e0350; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_SCANLINE_ORDERING: u32 = 0xc01e0352; +pub const SMB_NTSTATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED: u32 = 0xc01e0353; +pub const SMB_NTSTATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS: u32 = 0xc01e0354; +pub const SMB_NTSTATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT: u32 = 0xc01e0355; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM: u32 = 0xc01e0356; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN: u32 = 0xc01e0357; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT: u32 = 0xc01e0358; +pub const SMB_NTSTATUS_GRAPHICS_MAX_NUM_PATHS_REACHED: u32 = 0xc01e0359; +pub const SMB_NTSTATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION: u32 = 0xc01e035a; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_CLIENT_TYPE: u32 = 0xc01e035b; +pub const SMB_NTSTATUS_GRAPHICS_CLIENTVIDPN_NOT_SET: u32 = 0xc01e035c; +pub const SMB_NTSTATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED: u32 = 0xc01e0400; +pub const SMB_NTSTATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED: u32 = 0xc01e0401; +pub const SMB_NTSTATUS_GRAPHICS_NOT_A_LINKED_ADAPTER: u32 = 0xc01e0430; +pub const SMB_NTSTATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED: u32 = 0xc01e0431; +pub const SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED: u32 = 0xc01e0432; +pub const SMB_NTSTATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY: u32 = 0xc01e0433; +pub const SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_STARTED: u32 = 0xc01e0434; +pub const SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON: u32 = 0xc01e0435; +pub const SMB_NTSTATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE: u32 = 0xc01e0436; +pub const SMB_NTSTATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER: u32 = 0xc01e0438; +pub const SMB_NTSTATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED: u32 = 0xc01e043b; +pub const SMB_NTSTATUS_GRAPHICS_OPM_NOT_SUPPORTED: u32 = 0xc01e0500; +pub const SMB_NTSTATUS_GRAPHICS_COPP_NOT_SUPPORTED: u32 = 0xc01e0501; +pub const SMB_NTSTATUS_GRAPHICS_UAB_NOT_SUPPORTED: u32 = 0xc01e0502; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS: u32 = 0xc01e0503; +pub const SMB_NTSTATUS_GRAPHICS_OPM_PARAMETER_ARRAY_TOO_SMALL: u32 = 0xc01e0504; +pub const SMB_NTSTATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST: u32 = 0xc01e0505; +pub const SMB_NTSTATUS_GRAPHICS_PVP_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME: u32 = 0xc01e0506; +pub const SMB_NTSTATUS_GRAPHICS_PVP_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP: u32 = 0xc01e0507; +pub const SMB_NTSTATUS_GRAPHICS_PVP_MIRRORING_DEVICES_NOT_SUPPORTED: u32 = 0xc01e0508; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_POINTER: u32 = 0xc01e050a; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INTERNAL_ERROR: u32 = 0xc01e050b; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_HANDLE: u32 = 0xc01e050c; +pub const SMB_NTSTATUS_GRAPHICS_PVP_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE: u32 = 0xc01e050d; +pub const SMB_NTSTATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH: u32 = 0xc01e050e; +pub const SMB_NTSTATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED: u32 = 0xc01e050f; +pub const SMB_NTSTATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED: u32 = 0xc01e0510; +pub const SMB_NTSTATUS_GRAPHICS_PVP_HFS_FAILED: u32 = 0xc01e0511; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_SRM: u32 = 0xc01e0512; +pub const SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP: u32 = 0xc01e0513; +pub const SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP: u32 = 0xc01e0514; +pub const SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA: u32 = 0xc01e0515; +pub const SMB_NTSTATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET: u32 = 0xc01e0516; +pub const SMB_NTSTATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH: u32 = 0xc01e0517; +pub const SMB_NTSTATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE: u32 = 0xc01e0518; +pub const SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS: u32 = 0xc01e051a; +pub const SMB_NTSTATUS_GRAPHICS_OPM_SESSION_TYPE_CHANGE_IN_PROGRESS: u32 = 0xc01e051b; +pub const SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS: u32 = 0xc01e051c; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST: u32 = 0xc01e051d; +pub const SMB_NTSTATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR: u32 = 0xc01e051e; +pub const SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS: u32 = 0xc01e051f; +pub const SMB_NTSTATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED: u32 = 0xc01e0520; +pub const SMB_NTSTATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST: u32 = 0xc01e0521; +pub const SMB_NTSTATUS_GRAPHICS_I2C_NOT_SUPPORTED: u32 = 0xc01e0580; +pub const SMB_NTSTATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST: u32 = 0xc01e0581; +pub const SMB_NTSTATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA: u32 = 0xc01e0582; +pub const SMB_NTSTATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA: u32 = 0xc01e0583; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED: u32 = 0xc01e0584; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_DATA: u32 = 0xc01e0585; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE: u32 = 0xc01e0586; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING: u32 = 0xc01e0587; +pub const SMB_NTSTATUS_GRAPHICS_MCA_INTERNAL_ERROR: u32 = 0xc01e0588; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND: u32 = 0xc01e0589; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH: u32 = 0xc01e058a; +pub const SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM: u32 = 0xc01e058b; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE: u32 = 0xc01e058c; +pub const SMB_NTSTATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS: u32 = 0xc01e058d; +pub const SMB_NTSTATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED: u32 = 0xc01e05e0; +pub const SMB_NTSTATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME: u32 = 0xc01e05e1; +pub const SMB_NTSTATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP: u32 = 0xc01e05e2; +pub const SMB_NTSTATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED: u32 = 0xc01e05e3; +pub const SMB_NTSTATUS_GRAPHICS_INVALID_POINTER: u32 = 0xc01e05e4; +pub const SMB_NTSTATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE: u32 = 0xc01e05e5; +pub const SMB_NTSTATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL: u32 = 0xc01e05e6; +pub const SMB_NTSTATUS_GRAPHICS_INTERNAL_ERROR: u32 = 0xc01e05e7; +pub const SMB_NTSTATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS: u32 = 0xc01e05e8; +pub const SMB_NTSTATUS_FVE_LOCKED_VOLUME: u32 = 0xc0210000; +pub const SMB_NTSTATUS_FVE_NOT_ENCRYPTED: u32 = 0xc0210001; +pub const SMB_NTSTATUS_FVE_BAD_INFORMATION: u32 = 0xc0210002; +pub const SMB_NTSTATUS_FVE_TOO_SMALL: u32 = 0xc0210003; +pub const SMB_NTSTATUS_FVE_FAILED_WRONG_FS: u32 = 0xc0210004; +pub const SMB_NTSTATUS_FVE_FAILED_BAD_FS: u32 = 0xc0210005; +pub const SMB_NTSTATUS_FVE_FS_NOT_EXTENDED: u32 = 0xc0210006; +pub const SMB_NTSTATUS_FVE_FS_MOUNTED: u32 = 0xc0210007; +pub const SMB_NTSTATUS_FVE_NO_LICENSE: u32 = 0xc0210008; +pub const SMB_NTSTATUS_FVE_ACTION_NOT_ALLOWED: u32 = 0xc0210009; +pub const SMB_NTSTATUS_FVE_BAD_DATA: u32 = 0xc021000a; +pub const SMB_NTSTATUS_FVE_VOLUME_NOT_BOUND: u32 = 0xc021000b; +pub const SMB_NTSTATUS_FVE_NOT_DATA_VOLUME: u32 = 0xc021000c; +pub const SMB_NTSTATUS_FVE_CONV_READ_ERROR: u32 = 0xc021000d; +pub const SMB_NTSTATUS_FVE_CONV_WRITE_ERROR: u32 = 0xc021000e; +pub const SMB_NTSTATUS_FVE_OVERLAPPED_UPDATE: u32 = 0xc021000f; +pub const SMB_NTSTATUS_FVE_FAILED_SECTOR_SIZE: u32 = 0xc0210010; +pub const SMB_NTSTATUS_FVE_FAILED_AUTHENTICATION: u32 = 0xc0210011; +pub const SMB_NTSTATUS_FVE_NOT_OS_VOLUME: u32 = 0xc0210012; +pub const SMB_NTSTATUS_FVE_KEYFILE_NOT_FOUND: u32 = 0xc0210013; +pub const SMB_NTSTATUS_FVE_KEYFILE_INVALID: u32 = 0xc0210014; +pub const SMB_NTSTATUS_FVE_KEYFILE_NO_VMK: u32 = 0xc0210015; +pub const SMB_NTSTATUS_FVE_TPM_DISABLED: u32 = 0xc0210016; +pub const SMB_NTSTATUS_FVE_TPM_SRK_AUTH_NOT_ZERO: u32 = 0xc0210017; +pub const SMB_NTSTATUS_FVE_TPM_INVALID_PCR: u32 = 0xc0210018; +pub const SMB_NTSTATUS_FVE_TPM_NO_VMK: u32 = 0xc0210019; +pub const SMB_NTSTATUS_FVE_PIN_INVALID: u32 = 0xc021001a; +pub const SMB_NTSTATUS_FVE_AUTH_INVALID_APPLICATION: u32 = 0xc021001b; +pub const SMB_NTSTATUS_FVE_AUTH_INVALID_CONFIG: u32 = 0xc021001c; +pub const SMB_NTSTATUS_FVE_DEBUGGER_ENABLED: u32 = 0xc021001d; +pub const SMB_NTSTATUS_FVE_DRY_RUN_FAILED: u32 = 0xc021001e; +pub const SMB_NTSTATUS_FVE_BAD_METADATA_POINTER: u32 = 0xc021001f; +pub const SMB_NTSTATUS_FVE_OLD_METADATA_COPY: u32 = 0xc0210020; +pub const SMB_NTSTATUS_FVE_REBOOT_REQUIRED: u32 = 0xc0210021; +pub const SMB_NTSTATUS_FVE_RAW_ACCESS: u32 = 0xc0210022; +pub const SMB_NTSTATUS_FVE_RAW_BLOCKED: u32 = 0xc0210023; +pub const SMB_NTSTATUS_FVE_NO_FEATURE_LICENSE: u32 = 0xc0210026; +pub const SMB_NTSTATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED: u32 = 0xc0210027; +pub const SMB_NTSTATUS_FVE_CONV_RECOVERY_FAILED: u32 = 0xc0210028; +pub const SMB_NTSTATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG: u32 = 0xc0210029; +pub const SMB_NTSTATUS_FVE_VOLUME_TOO_SMALL: u32 = 0xc0210030; +pub const SMB_NTSTATUS_FWP_CALLOUT_NOT_FOUND: u32 = 0xc0220001; +pub const SMB_NTSTATUS_FWP_CONDITION_NOT_FOUND: u32 = 0xc0220002; +pub const SMB_NTSTATUS_FWP_FILTER_NOT_FOUND: u32 = 0xc0220003; +pub const SMB_NTSTATUS_FWP_LAYER_NOT_FOUND: u32 = 0xc0220004; +pub const SMB_NTSTATUS_FWP_PROVIDER_NOT_FOUND: u32 = 0xc0220005; +pub const SMB_NTSTATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND: u32 = 0xc0220006; +pub const SMB_NTSTATUS_FWP_SUBLAYER_NOT_FOUND: u32 = 0xc0220007; +pub const SMB_NTSTATUS_FWP_NOT_FOUND: u32 = 0xc0220008; +pub const SMB_NTSTATUS_FWP_ALREADY_EXISTS: u32 = 0xc0220009; +pub const SMB_NTSTATUS_FWP_IN_USE: u32 = 0xc022000a; +pub const SMB_NTSTATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS: u32 = 0xc022000b; +pub const SMB_NTSTATUS_FWP_WRONG_SESSION: u32 = 0xc022000c; +pub const SMB_NTSTATUS_FWP_NO_TXN_IN_PROGRESS: u32 = 0xc022000d; +pub const SMB_NTSTATUS_FWP_TXN_IN_PROGRESS: u32 = 0xc022000e; +pub const SMB_NTSTATUS_FWP_TXN_ABORTED: u32 = 0xc022000f; +pub const SMB_NTSTATUS_FWP_SESSION_ABORTED: u32 = 0xc0220010; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_TXN: u32 = 0xc0220011; +pub const SMB_NTSTATUS_FWP_TIMEOUT: u32 = 0xc0220012; +pub const SMB_NTSTATUS_FWP_NET_EVENTS_DISABLED: u32 = 0xc0220013; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_LAYER: u32 = 0xc0220014; +pub const SMB_NTSTATUS_FWP_KM_CLIENTS_ONLY: u32 = 0xc0220015; +pub const SMB_NTSTATUS_FWP_LIFETIME_MISMATCH: u32 = 0xc0220016; +pub const SMB_NTSTATUS_FWP_BUILTIN_OBJECT: u32 = 0xc0220017; +pub const SMB_NTSTATUS_FWP_TOO_MANY_BOOTTIME_FILTERS: u32 = 0xc0220018; +pub const SMB_NTSTATUS_FWP_NOTIFICATION_DROPPED: u32 = 0xc0220019; +pub const SMB_NTSTATUS_FWP_TRAFFIC_MISMATCH: u32 = 0xc022001a; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_SA_STATE: u32 = 0xc022001b; +pub const SMB_NTSTATUS_FWP_NULL_POINTER: u32 = 0xc022001c; +pub const SMB_NTSTATUS_FWP_INVALID_ENUMERATOR: u32 = 0xc022001d; +pub const SMB_NTSTATUS_FWP_INVALID_FLAGS: u32 = 0xc022001e; +pub const SMB_NTSTATUS_FWP_INVALID_NET_MASK: u32 = 0xc022001f; +pub const SMB_NTSTATUS_FWP_INVALID_RANGE: u32 = 0xc0220020; +pub const SMB_NTSTATUS_FWP_INVALID_INTERVAL: u32 = 0xc0220021; +pub const SMB_NTSTATUS_FWP_ZERO_LENGTH_ARRAY: u32 = 0xc0220022; +pub const SMB_NTSTATUS_FWP_NULL_DISPLAY_NAME: u32 = 0xc0220023; +pub const SMB_NTSTATUS_FWP_INVALID_ACTION_TYPE: u32 = 0xc0220024; +pub const SMB_NTSTATUS_FWP_INVALID_WEIGHT: u32 = 0xc0220025; +pub const SMB_NTSTATUS_FWP_MATCH_TYPE_MISMATCH: u32 = 0xc0220026; +pub const SMB_NTSTATUS_FWP_TYPE_MISMATCH: u32 = 0xc0220027; +pub const SMB_NTSTATUS_FWP_OUT_OF_BOUNDS: u32 = 0xc0220028; +pub const SMB_NTSTATUS_FWP_RESERVED: u32 = 0xc0220029; +pub const SMB_NTSTATUS_FWP_DUPLICATE_CONDITION: u32 = 0xc022002a; +pub const SMB_NTSTATUS_FWP_DUPLICATE_KEYMOD: u32 = 0xc022002b; +pub const SMB_NTSTATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER: u32 = 0xc022002c; +pub const SMB_NTSTATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER: u32 = 0xc022002d; +pub const SMB_NTSTATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER: u32 = 0xc022002e; +pub const SMB_NTSTATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT: u32 = 0xc022002f; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_AUTH_METHOD: u32 = 0xc0220030; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_DH_GROUP: u32 = 0xc0220031; +pub const SMB_NTSTATUS_FWP_EM_NOT_SUPPORTED: u32 = 0xc0220032; +pub const SMB_NTSTATUS_FWP_NEVER_MATCH: u32 = 0xc0220033; +pub const SMB_NTSTATUS_FWP_PROVIDER_CONTEXT_MISMATCH: u32 = 0xc0220034; +pub const SMB_NTSTATUS_FWP_INVALID_PARAMETER: u32 = 0xc0220035; +pub const SMB_NTSTATUS_FWP_TOO_MANY_SUBLAYERS: u32 = 0xc0220036; +pub const SMB_NTSTATUS_FWP_CALLOUT_NOTIFICATION_FAILED: u32 = 0xc0220037; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_AUTH_CONFIG: u32 = 0xc0220038; +pub const SMB_NTSTATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG: u32 = 0xc0220039; +pub const SMB_NTSTATUS_FWP_DUPLICATE_AUTH_METHOD: u32 = 0xc022003c; +pub const SMB_NTSTATUS_FWP_TCPIP_NOT_READY: u32 = 0xc0220100; +pub const SMB_NTSTATUS_FWP_INJECT_HANDLE_CLOSING: u32 = 0xc0220101; +pub const SMB_NTSTATUS_FWP_INJECT_HANDLE_STALE: u32 = 0xc0220102; +pub const SMB_NTSTATUS_FWP_CANNOT_PEND: u32 = 0xc0220103; +pub const SMB_NTSTATUS_NDIS_CLOSING: u32 = 0xc0230002; +pub const SMB_NTSTATUS_NDIS_BAD_VERSION: u32 = 0xc0230004; +pub const SMB_NTSTATUS_NDIS_BAD_CHARACTERISTICS: u32 = 0xc0230005; +pub const SMB_NTSTATUS_NDIS_ADAPTER_NOT_FOUND: u32 = 0xc0230006; +pub const SMB_NTSTATUS_NDIS_OPEN_FAILED: u32 = 0xc0230007; +pub const SMB_NTSTATUS_NDIS_DEVICE_FAILED: u32 = 0xc0230008; +pub const SMB_NTSTATUS_NDIS_MULTICAST_FULL: u32 = 0xc0230009; +pub const SMB_NTSTATUS_NDIS_MULTICAST_EXISTS: u32 = 0xc023000a; +pub const SMB_NTSTATUS_NDIS_MULTICAST_NOT_FOUND: u32 = 0xc023000b; +pub const SMB_NTSTATUS_NDIS_REQUEST_ABORTED: u32 = 0xc023000c; +pub const SMB_NTSTATUS_NDIS_RESET_IN_PROGRESS: u32 = 0xc023000d; +pub const SMB_NTSTATUS_NDIS_INVALID_PACKET: u32 = 0xc023000f; +pub const SMB_NTSTATUS_NDIS_INVALID_DEVICE_REQUEST: u32 = 0xc0230010; +pub const SMB_NTSTATUS_NDIS_ADAPTER_NOT_READY: u32 = 0xc0230011; +pub const SMB_NTSTATUS_NDIS_INVALID_LENGTH: u32 = 0xc0230014; +pub const SMB_NTSTATUS_NDIS_INVALID_DATA: u32 = 0xc0230015; +pub const SMB_NTSTATUS_NDIS_BUFFER_TOO_SHORT: u32 = 0xc0230016; +pub const SMB_NTSTATUS_NDIS_INVALID_OID: u32 = 0xc0230017; +pub const SMB_NTSTATUS_NDIS_ADAPTER_REMOVED: u32 = 0xc0230018; +pub const SMB_NTSTATUS_NDIS_UNSUPPORTED_MEDIA: u32 = 0xc0230019; +pub const SMB_NTSTATUS_NDIS_GROUP_ADDRESS_IN_USE: u32 = 0xc023001a; +pub const SMB_NTSTATUS_NDIS_FILE_NOT_FOUND: u32 = 0xc023001b; +pub const SMB_NTSTATUS_NDIS_ERROR_READING_FILE: u32 = 0xc023001c; +pub const SMB_NTSTATUS_NDIS_ALREADY_MAPPED: u32 = 0xc023001d; +pub const SMB_NTSTATUS_NDIS_RESOURCE_CONFLICT: u32 = 0xc023001e; +pub const SMB_NTSTATUS_NDIS_MEDIA_DISCONNECTED: u32 = 0xc023001f; +pub const SMB_NTSTATUS_NDIS_INVALID_ADDRESS: u32 = 0xc0230022; +pub const SMB_NTSTATUS_NDIS_PAUSED: u32 = 0xc023002a; +pub const SMB_NTSTATUS_NDIS_INTERFACE_NOT_FOUND: u32 = 0xc023002b; +pub const SMB_NTSTATUS_NDIS_UNSUPPORTED_REVISION: u32 = 0xc023002c; +pub const SMB_NTSTATUS_NDIS_INVALID_PORT: u32 = 0xc023002d; +pub const SMB_NTSTATUS_NDIS_INVALID_PORT_STATE: u32 = 0xc023002e; +pub const SMB_NTSTATUS_NDIS_LOW_POWER_STATE: u32 = 0xc023002f; +pub const SMB_NTSTATUS_NDIS_NOT_SUPPORTED: u32 = 0xc02300bb; +pub const SMB_NTSTATUS_NDIS_OFFLOAD_POLICY: u32 = 0xc023100f; +pub const SMB_NTSTATUS_NDIS_OFFLOAD_CONNECTION_REJECTED: u32 = 0xc0231012; +pub const SMB_NTSTATUS_NDIS_OFFLOAD_PATH_REJECTED: u32 = 0xc0231013; +pub const SMB_NTSTATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED: u32 = 0xc0232000; +pub const SMB_NTSTATUS_NDIS_DOT11_MEDIA_IN_USE: u32 = 0xc0232001; +pub const SMB_NTSTATUS_NDIS_DOT11_POWER_STATE_INVALID: u32 = 0xc0232002; +pub const SMB_NTSTATUS_NDIS_PM_WOL_PATTERN_LIST_FULL: u32 = 0xc0232003; +pub const SMB_NTSTATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL: u32 = 0xc0232004; +pub const SMB_NTSTATUS_IPSEC_BAD_SPI: u32 = 0xc0360001; +pub const SMB_NTSTATUS_IPSEC_SA_LIFETIME_EXPIRED: u32 = 0xc0360002; +pub const SMB_NTSTATUS_IPSEC_WRONG_SA: u32 = 0xc0360003; +pub const SMB_NTSTATUS_IPSEC_REPLAY_CHECK_FAILED: u32 = 0xc0360004; +pub const SMB_NTSTATUS_IPSEC_INVALID_PACKET: u32 = 0xc0360005; +pub const SMB_NTSTATUS_IPSEC_INTEGRITY_CHECK_FAILED: u32 = 0xc0360006; +pub const SMB_NTSTATUS_IPSEC_CLEAR_TEXT_DROP: u32 = 0xc0360007; +pub const SMB_NTSTATUS_IPSEC_AUTH_FIREWALL_DROP: u32 = 0xc0360008; +pub const SMB_NTSTATUS_IPSEC_THROTTLE_DROP: u32 = 0xc0360009; +pub const SMB_NTSTATUS_IPSEC_DOSP_BLOCK: u32 = 0xc0368000; +pub const SMB_NTSTATUS_IPSEC_DOSP_RECEIVED_MULTICAST: u32 = 0xc0368001; +pub const SMB_NTSTATUS_IPSEC_DOSP_INVALID_PACKET: u32 = 0xc0368002; +pub const SMB_NTSTATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED: u32 = 0xc0368003; +pub const SMB_NTSTATUS_IPSEC_DOSP_MAX_ENTRIES: u32 = 0xc0368004; +pub const SMB_NTSTATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED: u32 = 0xc0368005; +pub const SMB_NTSTATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES: u32 = 0xc0368006; +pub const SMB_NTSTATUS_VOLMGR_MIRROR_NOT_SUPPORTED: u32 = 0xc038005b; +pub const SMB_NTSTATUS_VOLMGR_RAID5_NOT_SUPPORTED: u32 = 0xc038005c; +pub const SMB_NTSTATUS_VIRTDISK_PROVIDER_NOT_FOUND: u32 = 0xc03a0014; +pub const SMB_NTSTATUS_VIRTDISK_NOT_VIRTUAL_DISK: u32 = 0xc03a0015; +pub const SMB_NTSTATUS_VHD_PARENT_VHD_ACCESS_DENIED: u32 = 0xc03a0016; +pub const SMB_NTSTATUS_VHD_CHILD_PARENT_SIZE_MISMATCH: u32 = 0xc03a0017; +pub const SMB_NTSTATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED: u32 = 0xc03a0018; +pub const SMB_NTSTATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT: u32 = 0xc03a0019; + +pub fn smb_ntstatus_string(c: u32) -> Option<&'static str> { + match c { + SMB_NTSTATUS_SUCCESS => Some("STATUS_SUCCESS"), + SMB_NTSTATUS_WAIT_1 => Some("STATUS_WAIT_1"), + SMB_NTSTATUS_WAIT_2 => Some("STATUS_WAIT_2"), + SMB_NTSTATUS_WAIT_3 => Some("STATUS_WAIT_3"), + SMB_NTSTATUS_WAIT_63 => Some("STATUS_WAIT_63"), + SMB_NTSTATUS_ABANDONED => Some("STATUS_ABANDONED"), + SMB_NTSTATUS_ABANDONED_WAIT_63 => Some("STATUS_ABANDONED_WAIT_63"), + SMB_NTSTATUS_USER_APC => Some("STATUS_USER_APC"), + SMB_NTSTATUS_ALERTED => Some("STATUS_ALERTED"), + SMB_NTSTATUS_TIMEOUT => Some("STATUS_TIMEOUT"), + SMB_NTSTATUS_PENDING => Some("STATUS_PENDING"), + SMB_NTSTATUS_REPARSE => Some("STATUS_REPARSE"), + SMB_NTSTATUS_MORE_ENTRIES => Some("STATUS_MORE_ENTRIES"), + SMB_NTSTATUS_NOT_ALL_ASSIGNED => Some("STATUS_NOT_ALL_ASSIGNED"), + SMB_NTSTATUS_SOME_NOT_MAPPED => Some("STATUS_SOME_NOT_MAPPED"), + SMB_NTSTATUS_OPLOCK_BREAK_IN_PROGRESS => Some("STATUS_OPLOCK_BREAK_IN_PROGRESS"), + SMB_NTSTATUS_VOLUME_MOUNTED => Some("STATUS_VOLUME_MOUNTED"), + SMB_NTSTATUS_RXACT_COMMITTED => Some("STATUS_RXACT_COMMITTED"), + SMB_NTSTATUS_NOTIFY_CLEANUP => Some("STATUS_NOTIFY_CLEANUP"), + SMB_NTSTATUS_NOTIFY_ENUM_DIR => Some("STATUS_NOTIFY_ENUM_DIR"), + SMB_NTSTATUS_NO_QUOTAS_FOR_ACCOUNT => Some("STATUS_NO_QUOTAS_FOR_ACCOUNT"), + SMB_NTSTATUS_PRIMARY_TRANSPORT_CONNECT_FAILED => Some("STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED"), + SMB_NTSTATUS_PAGE_FAULT_TRANSITION => Some("STATUS_PAGE_FAULT_TRANSITION"), + SMB_NTSTATUS_PAGE_FAULT_DEMAND_ZERO => Some("STATUS_PAGE_FAULT_DEMAND_ZERO"), + SMB_NTSTATUS_PAGE_FAULT_COPY_ON_WRITE => Some("STATUS_PAGE_FAULT_COPY_ON_WRITE"), + SMB_NTSTATUS_PAGE_FAULT_GUARD_PAGE => Some("STATUS_PAGE_FAULT_GUARD_PAGE"), + SMB_NTSTATUS_PAGE_FAULT_PAGING_FILE => Some("STATUS_PAGE_FAULT_PAGING_FILE"), + SMB_NTSTATUS_CACHE_PAGE_LOCKED => Some("STATUS_CACHE_PAGE_LOCKED"), + SMB_NTSTATUS_CRASH_DUMP => Some("STATUS_CRASH_DUMP"), + SMB_NTSTATUS_BUFFER_ALL_ZEROS => Some("STATUS_BUFFER_ALL_ZEROS"), + SMB_NTSTATUS_REPARSE_OBJECT => Some("STATUS_REPARSE_OBJECT"), + SMB_NTSTATUS_RESOURCE_REQUIREMENTS_CHANGED => Some("STATUS_RESOURCE_REQUIREMENTS_CHANGED"), + SMB_NTSTATUS_TRANSLATION_COMPLETE => Some("STATUS_TRANSLATION_COMPLETE"), + SMB_NTSTATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY => Some("STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY"), + SMB_NTSTATUS_NOTHING_TO_TERMINATE => Some("STATUS_NOTHING_TO_TERMINATE"), + SMB_NTSTATUS_PROCESS_NOT_IN_JOB => Some("STATUS_PROCESS_NOT_IN_JOB"), + SMB_NTSTATUS_PROCESS_IN_JOB => Some("STATUS_PROCESS_IN_JOB"), + SMB_NTSTATUS_VOLSNAP_HIBERNATE_READY => Some("STATUS_VOLSNAP_HIBERNATE_READY"), + SMB_NTSTATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY => Some("STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY"), + SMB_NTSTATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED => Some("STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED"), + SMB_NTSTATUS_INTERRUPT_STILL_CONNECTED => Some("STATUS_INTERRUPT_STILL_CONNECTED"), + SMB_NTSTATUS_PROCESS_CLONED => Some("STATUS_PROCESS_CLONED"), + SMB_NTSTATUS_FILE_LOCKED_WITH_ONLY_READERS => Some("STATUS_FILE_LOCKED_WITH_ONLY_READERS"), + SMB_NTSTATUS_FILE_LOCKED_WITH_WRITERS => Some("STATUS_FILE_LOCKED_WITH_WRITERS"), + SMB_NTSTATUS_RESOURCEMANAGER_READ_ONLY => Some("STATUS_RESOURCEMANAGER_READ_ONLY"), + SMB_NTSTATUS_WAIT_FOR_OPLOCK => Some("STATUS_WAIT_FOR_OPLOCK"), + SMB_NTDBG_EXCEPTION_HANDLED => Some("DBG_EXCEPTION_HANDLED"), + SMB_NTDBG_CONTINUE => Some("DBG_CONTINUE"), + SMB_NTSTATUS_FLT_IO_COMPLETE => Some("STATUS_FLT_IO_COMPLETE"), + SMB_NTSTATUS_FILE_NOT_AVAILABLE => Some("STATUS_FILE_NOT_AVAILABLE"), + SMB_NTSTATUS_SHARE_UNAVAILABLE => Some("STATUS_SHARE_UNAVAILABLE"), + SMB_NTSTATUS_CALLBACK_RETURNED_THREAD_AFFINITY => Some("STATUS_CALLBACK_RETURNED_THREAD_AFFINITY"), + SMB_NTSTATUS_OBJECT_NAME_EXISTS => Some("STATUS_OBJECT_NAME_EXISTS"), + SMB_NTSTATUS_THREAD_WAS_SUSPENDED => Some("STATUS_THREAD_WAS_SUSPENDED"), + SMB_NTSTATUS_WORKING_SET_LIMIT_RANGE => Some("STATUS_WORKING_SET_LIMIT_RANGE"), + SMB_NTSTATUS_IMAGE_NOT_AT_BASE => Some("STATUS_IMAGE_NOT_AT_BASE"), + SMB_NTSTATUS_RXACT_STATE_CREATED => Some("STATUS_RXACT_STATE_CREATED"), + SMB_NTSTATUS_SEGMENT_NOTIFICATION => Some("STATUS_SEGMENT_NOTIFICATION"), + SMB_NTSTATUS_LOCAL_USER_SESSION_KEY => Some("STATUS_LOCAL_USER_SESSION_KEY"), + SMB_NTSTATUS_BAD_CURRENT_DIRECTORY => Some("STATUS_BAD_CURRENT_DIRECTORY"), + SMB_NTSTATUS_SERIAL_MORE_WRITES => Some("STATUS_SERIAL_MORE_WRITES"), + SMB_NTSTATUS_REGISTRY_RECOVERED => Some("STATUS_REGISTRY_RECOVERED"), + SMB_NTSTATUS_FT_READ_RECOVERY_FROM_BACKUP => Some("STATUS_FT_READ_RECOVERY_FROM_BACKUP"), + SMB_NTSTATUS_FT_WRITE_RECOVERY => Some("STATUS_FT_WRITE_RECOVERY"), + SMB_NTSTATUS_SERIAL_COUNTER_TIMEOUT => Some("STATUS_SERIAL_COUNTER_TIMEOUT"), + SMB_NTSTATUS_NULL_LM_PASSWORD => Some("STATUS_NULL_LM_PASSWORD"), + SMB_NTSTATUS_IMAGE_MACHINE_TYPE_MISMATCH => Some("STATUS_IMAGE_MACHINE_TYPE_MISMATCH"), + SMB_NTSTATUS_RECEIVE_PARTIAL => Some("STATUS_RECEIVE_PARTIAL"), + SMB_NTSTATUS_RECEIVE_EXPEDITED => Some("STATUS_RECEIVE_EXPEDITED"), + SMB_NTSTATUS_RECEIVE_PARTIAL_EXPEDITED => Some("STATUS_RECEIVE_PARTIAL_EXPEDITED"), + SMB_NTSTATUS_EVENT_DONE => Some("STATUS_EVENT_DONE"), + SMB_NTSTATUS_EVENT_PENDING => Some("STATUS_EVENT_PENDING"), + SMB_NTSTATUS_CHECKING_FILE_SYSTEM => Some("STATUS_CHECKING_FILE_SYSTEM"), + SMB_NTSTATUS_FATAL_APP_EXIT => Some("STATUS_FATAL_APP_EXIT"), + SMB_NTSTATUS_PREDEFINED_HANDLE => Some("STATUS_PREDEFINED_HANDLE"), + SMB_NTSTATUS_WAS_UNLOCKED => Some("STATUS_WAS_UNLOCKED"), + SMB_NTSTATUS_SERVICE_NOTIFICATION => Some("STATUS_SERVICE_NOTIFICATION"), + SMB_NTSTATUS_WAS_LOCKED => Some("STATUS_WAS_LOCKED"), + SMB_NTSTATUS_LOG_HARD_ERROR => Some("STATUS_LOG_HARD_ERROR"), + SMB_NTSTATUS_ALREADY_WIN32 => Some("STATUS_ALREADY_WIN32"), + SMB_NTSTATUS_WX86_UNSIMULATE => Some("STATUS_WX86_UNSIMULATE"), + SMB_NTSTATUS_WX86_CONTINUE => Some("STATUS_WX86_CONTINUE"), + SMB_NTSTATUS_WX86_SINGLE_STEP => Some("STATUS_WX86_SINGLE_STEP"), + SMB_NTSTATUS_WX86_BREAKPOINT => Some("STATUS_WX86_BREAKPOINT"), + SMB_NTSTATUS_WX86_EXCEPTION_CONTINUE => Some("STATUS_WX86_EXCEPTION_CONTINUE"), + SMB_NTSTATUS_WX86_EXCEPTION_LASTCHANCE => Some("STATUS_WX86_EXCEPTION_LASTCHANCE"), + SMB_NTSTATUS_WX86_EXCEPTION_CHAIN => Some("STATUS_WX86_EXCEPTION_CHAIN"), + SMB_NTSTATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE => Some("STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE"), + SMB_NTSTATUS_NO_YIELD_PERFORMED => Some("STATUS_NO_YIELD_PERFORMED"), + SMB_NTSTATUS_TIMER_RESUME_IGNORED => Some("STATUS_TIMER_RESUME_IGNORED"), + SMB_NTSTATUS_ARBITRATION_UNHANDLED => Some("STATUS_ARBITRATION_UNHANDLED"), + SMB_NTSTATUS_CARDBUS_NOT_SUPPORTED => Some("STATUS_CARDBUS_NOT_SUPPORTED"), + SMB_NTSTATUS_WX86_CREATEWX86TIB => Some("STATUS_WX86_CREATEWX86TIB"), + SMB_NTSTATUS_MP_PROCESSOR_MISMATCH => Some("STATUS_MP_PROCESSOR_MISMATCH"), + SMB_NTSTATUS_HIBERNATED => Some("STATUS_HIBERNATED"), + SMB_NTSTATUS_RESUME_HIBERNATION => Some("STATUS_RESUME_HIBERNATION"), + SMB_NTSTATUS_FIRMWARE_UPDATED => Some("STATUS_FIRMWARE_UPDATED"), + SMB_NTSTATUS_DRIVERS_LEAKING_LOCKED_PAGES => Some("STATUS_DRIVERS_LEAKING_LOCKED_PAGES"), + SMB_NTSTATUS_MESSAGE_RETRIEVED => Some("STATUS_MESSAGE_RETRIEVED"), + SMB_NTSTATUS_SYSTEM_POWERSTATE_TRANSITION => Some("STATUS_SYSTEM_POWERSTATE_TRANSITION"), + SMB_NTSTATUS_ALPC_CHECK_COMPLETION_LIST => Some("STATUS_ALPC_CHECK_COMPLETION_LIST"), + SMB_NTSTATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION => Some("STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION"), + SMB_NTSTATUS_ACCESS_AUDIT_BY_POLICY => Some("STATUS_ACCESS_AUDIT_BY_POLICY"), + SMB_NTSTATUS_ABANDON_HIBERFILE => Some("STATUS_ABANDON_HIBERFILE"), + SMB_NTSTATUS_BIZRULES_NOT_ENABLED => Some("STATUS_BIZRULES_NOT_ENABLED"), + SMB_NTSTATUS_WAKE_SYSTEM => Some("STATUS_WAKE_SYSTEM"), + SMB_NTSTATUS_DS_SHUTTING_DOWN => Some("STATUS_DS_SHUTTING_DOWN"), + SMB_NTDBG_REPLY_LATER => Some("DBG_REPLY_LATER"), + SMB_NTDBG_UNABLE_TO_PROVIDE_HANDLE => Some("DBG_UNABLE_TO_PROVIDE_HANDLE"), + SMB_NTDBG_TERMINATE_THREAD => Some("DBG_TERMINATE_THREAD"), + SMB_NTDBG_TERMINATE_PROCESS => Some("DBG_TERMINATE_PROCESS"), + SMB_NTDBG_CONTROL_C => Some("DBG_CONTROL_C"), + SMB_NTDBG_PRINTEXCEPTION_C => Some("DBG_PRINTEXCEPTION_C"), + SMB_NTDBG_RIPEXCEPTION => Some("DBG_RIPEXCEPTION"), + SMB_NTDBG_CONTROL_BREAK => Some("DBG_CONTROL_BREAK"), + SMB_NTDBG_COMMAND_EXCEPTION => Some("DBG_COMMAND_EXCEPTION"), + SMB_NTRPC_NT_UUID_LOCAL_ONLY => Some("RPC_NT_UUID_LOCAL_ONLY"), + SMB_NTRPC_NT_SEND_INCOMPLETE => Some("RPC_NT_SEND_INCOMPLETE"), + SMB_NTSTATUS_CTX_CDM_CONNECT => Some("STATUS_CTX_CDM_CONNECT"), + SMB_NTSTATUS_CTX_CDM_DISCONNECT => Some("STATUS_CTX_CDM_DISCONNECT"), + SMB_NTSTATUS_SXS_RELEASE_ACTIVATION_CONTEXT => Some("STATUS_SXS_RELEASE_ACTIVATION_CONTEXT"), + SMB_NTSTATUS_RECOVERY_NOT_NEEDED => Some("STATUS_RECOVERY_NOT_NEEDED"), + SMB_NTSTATUS_RM_ALREADY_STARTED => Some("STATUS_RM_ALREADY_STARTED"), + SMB_NTSTATUS_LOG_NO_RESTART => Some("STATUS_LOG_NO_RESTART"), + SMB_NTSTATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST => Some("STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST"), + SMB_NTSTATUS_GRAPHICS_PARTIAL_DATA_POPULATED => Some("STATUS_GRAPHICS_PARTIAL_DATA_POPULATED"), + SMB_NTSTATUS_GRAPHICS_DRIVER_MISMATCH => Some("STATUS_GRAPHICS_DRIVER_MISMATCH"), + SMB_NTSTATUS_GRAPHICS_MODE_NOT_PINNED => Some("STATUS_GRAPHICS_MODE_NOT_PINNED"), + SMB_NTSTATUS_GRAPHICS_NO_PREFERRED_MODE => Some("STATUS_GRAPHICS_NO_PREFERRED_MODE"), + SMB_NTSTATUS_GRAPHICS_DATASET_IS_EMPTY => Some("STATUS_GRAPHICS_DATASET_IS_EMPTY"), + SMB_NTSTATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET => Some("STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET"), + SMB_NTSTATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED => Some("STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED"), + SMB_NTSTATUS_GRAPHICS_UNKNOWN_CHILD_STATUS => Some("STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS"), + SMB_NTSTATUS_GRAPHICS_LEADLINK_START_DEFERRED => Some("STATUS_GRAPHICS_LEADLINK_START_DEFERRED"), + SMB_NTSTATUS_GRAPHICS_POLLING_TOO_FREQUENTLY => Some("STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY"), + SMB_NTSTATUS_GRAPHICS_START_DEFERRED => Some("STATUS_GRAPHICS_START_DEFERRED"), + SMB_NTSTATUS_NDIS_INDICATION_REQUIRED => Some("STATUS_NDIS_INDICATION_REQUIRED"), + SMB_NTSTATUS_GUARD_PAGE_VIOLATION => Some("STATUS_GUARD_PAGE_VIOLATION"), + SMB_NTSTATUS_DATATYPE_MISALIGNMENT => Some("STATUS_DATATYPE_MISALIGNMENT"), + SMB_NTSTATUS_BREAKPOINT => Some("STATUS_BREAKPOINT"), + SMB_NTSTATUS_SINGLE_STEP => Some("STATUS_SINGLE_STEP"), + SMB_NTSTATUS_BUFFER_OVERFLOW => Some("STATUS_BUFFER_OVERFLOW"), + SMB_NTSTATUS_NO_MORE_FILES => Some("STATUS_NO_MORE_FILES"), + SMB_NTSTATUS_WAKE_SYSTEM_DEBUGGER => Some("STATUS_WAKE_SYSTEM_DEBUGGER"), + SMB_NTSTATUS_HANDLES_CLOSED => Some("STATUS_HANDLES_CLOSED"), + SMB_NTSTATUS_NO_INHERITANCE => Some("STATUS_NO_INHERITANCE"), + SMB_NTSTATUS_GUID_SUBSTITUTION_MADE => Some("STATUS_GUID_SUBSTITUTION_MADE"), + SMB_NTSTATUS_PARTIAL_COPY => Some("STATUS_PARTIAL_COPY"), + SMB_NTSTATUS_DEVICE_PAPER_EMPTY => Some("STATUS_DEVICE_PAPER_EMPTY"), + SMB_NTSTATUS_DEVICE_POWERED_OFF => Some("STATUS_DEVICE_POWERED_OFF"), + SMB_NTSTATUS_DEVICE_OFF_LINE => Some("STATUS_DEVICE_OFF_LINE"), + SMB_NTSTATUS_DEVICE_BUSY => Some("STATUS_DEVICE_BUSY"), + SMB_NTSTATUS_NO_MORE_EAS => Some("STATUS_NO_MORE_EAS"), + SMB_NTSTATUS_INVALID_EA_NAME => Some("STATUS_INVALID_EA_NAME"), + SMB_NTSTATUS_EA_LIST_INCONSISTENT => Some("STATUS_EA_LIST_INCONSISTENT"), + SMB_NTSTATUS_INVALID_EA_FLAG => Some("STATUS_INVALID_EA_FLAG"), + SMB_NTSTATUS_VERIFY_REQUIRED => Some("STATUS_VERIFY_REQUIRED"), + SMB_NTSTATUS_EXTRANEOUS_INFORMATION => Some("STATUS_EXTRANEOUS_INFORMATION"), + SMB_NTSTATUS_RXACT_COMMIT_NECESSARY => Some("STATUS_RXACT_COMMIT_NECESSARY"), + SMB_NTSTATUS_NO_MORE_ENTRIES => Some("STATUS_NO_MORE_ENTRIES"), + SMB_NTSTATUS_FILEMARK_DETECTED => Some("STATUS_FILEMARK_DETECTED"), + SMB_NTSTATUS_MEDIA_CHANGED => Some("STATUS_MEDIA_CHANGED"), + SMB_NTSTATUS_BUS_RESET => Some("STATUS_BUS_RESET"), + SMB_NTSTATUS_END_OF_MEDIA => Some("STATUS_END_OF_MEDIA"), + SMB_NTSTATUS_BEGINNING_OF_MEDIA => Some("STATUS_BEGINNING_OF_MEDIA"), + SMB_NTSTATUS_MEDIA_CHECK => Some("STATUS_MEDIA_CHECK"), + SMB_NTSTATUS_SETMARK_DETECTED => Some("STATUS_SETMARK_DETECTED"), + SMB_NTSTATUS_NO_DATA_DETECTED => Some("STATUS_NO_DATA_DETECTED"), + SMB_NTSTATUS_REDIRECTOR_HAS_OPEN_HANDLES => Some("STATUS_REDIRECTOR_HAS_OPEN_HANDLES"), + SMB_NTSTATUS_SERVER_HAS_OPEN_HANDLES => Some("STATUS_SERVER_HAS_OPEN_HANDLES"), + SMB_NTSTATUS_ALREADY_DISCONNECTED => Some("STATUS_ALREADY_DISCONNECTED"), + SMB_NTSTATUS_LONGJUMP => Some("STATUS_LONGJUMP"), + SMB_NTSTATUS_CLEANER_CARTRIDGE_INSTALLED => Some("STATUS_CLEANER_CARTRIDGE_INSTALLED"), + SMB_NTSTATUS_PLUGPLAY_QUERY_VETOED => Some("STATUS_PLUGPLAY_QUERY_VETOED"), + SMB_NTSTATUS_UNWIND_CONSOLIDATE => Some("STATUS_UNWIND_CONSOLIDATE"), + SMB_NTSTATUS_REGISTRY_HIVE_RECOVERED => Some("STATUS_REGISTRY_HIVE_RECOVERED"), + SMB_NTSTATUS_DLL_MIGHT_BE_INSECURE => Some("STATUS_DLL_MIGHT_BE_INSECURE"), + SMB_NTSTATUS_DLL_MIGHT_BE_INCOMPATIBLE => Some("STATUS_DLL_MIGHT_BE_INCOMPATIBLE"), + SMB_NTSTATUS_STOPPED_ON_SYMLINK => Some("STATUS_STOPPED_ON_SYMLINK"), + SMB_NTSTATUS_DEVICE_REQUIRES_CLEANING => Some("STATUS_DEVICE_REQUIRES_CLEANING"), + SMB_NTSTATUS_DEVICE_DOOR_OPEN => Some("STATUS_DEVICE_DOOR_OPEN"), + SMB_NTSTATUS_DATA_LOST_REPAIR => Some("STATUS_DATA_LOST_REPAIR"), + SMB_NTDBG_EXCEPTION_NOT_HANDLED => Some("DBG_EXCEPTION_NOT_HANDLED"), + SMB_NTSTATUS_CLUSTER_NODE_ALREADY_UP => Some("STATUS_CLUSTER_NODE_ALREADY_UP"), + SMB_NTSTATUS_CLUSTER_NODE_ALREADY_DOWN => Some("STATUS_CLUSTER_NODE_ALREADY_DOWN"), + SMB_NTSTATUS_CLUSTER_NETWORK_ALREADY_ONLINE => Some("STATUS_CLUSTER_NETWORK_ALREADY_ONLINE"), + SMB_NTSTATUS_CLUSTER_NETWORK_ALREADY_OFFLINE => Some("STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE"), + SMB_NTSTATUS_CLUSTER_NODE_ALREADY_MEMBER => Some("STATUS_CLUSTER_NODE_ALREADY_MEMBER"), + SMB_NTSTATUS_COULD_NOT_RESIZE_LOG => Some("STATUS_COULD_NOT_RESIZE_LOG"), + SMB_NTSTATUS_NO_TXF_METADATA => Some("STATUS_NO_TXF_METADATA"), + SMB_NTSTATUS_CANT_RECOVER_WITH_HANDLE_OPEN => Some("STATUS_CANT_RECOVER_WITH_HANDLE_OPEN"), + SMB_NTSTATUS_TXF_METADATA_ALREADY_PRESENT => Some("STATUS_TXF_METADATA_ALREADY_PRESENT"), + SMB_NTSTATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET => Some("STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET"), + SMB_NTSTATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED => Some("STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED"), + SMB_NTSTATUS_FLT_BUFFER_TOO_SMALL => Some("STATUS_FLT_BUFFER_TOO_SMALL"), + SMB_NTSTATUS_FVE_PARTIAL_METADATA => Some("STATUS_FVE_PARTIAL_METADATA"), + SMB_NTSTATUS_FVE_TRANSIENT_STATE => Some("STATUS_FVE_TRANSIENT_STATE"), + SMB_NTSTATUS_UNSUCCESSFUL => Some("STATUS_UNSUCCESSFUL"), + SMB_NTSTATUS_NOT_IMPLEMENTED => Some("STATUS_NOT_IMPLEMENTED"), + SMB_NTSTATUS_INVALID_INFO_CLASS => Some("STATUS_INVALID_INFO_CLASS"), + SMB_NTSTATUS_INFO_LENGTH_MISMATCH => Some("STATUS_INFO_LENGTH_MISMATCH"), + SMB_NTSTATUS_ACCESS_VIOLATION => Some("STATUS_ACCESS_VIOLATION"), + SMB_NTSTATUS_IN_PAGE_ERROR => Some("STATUS_IN_PAGE_ERROR"), + SMB_NTSTATUS_PAGEFILE_QUOTA => Some("STATUS_PAGEFILE_QUOTA"), + SMB_NTSTATUS_INVALID_HANDLE => Some("STATUS_INVALID_HANDLE"), + SMB_NTSTATUS_BAD_INITIAL_STACK => Some("STATUS_BAD_INITIAL_STACK"), + SMB_NTSTATUS_BAD_INITIAL_PC => Some("STATUS_BAD_INITIAL_PC"), + SMB_NTSTATUS_INVALID_CID => Some("STATUS_INVALID_CID"), + SMB_NTSTATUS_TIMER_NOT_CANCELED => Some("STATUS_TIMER_NOT_CANCELED"), + SMB_NTSTATUS_INVALID_PARAMETER => Some("STATUS_INVALID_PARAMETER"), + SMB_NTSTATUS_NO_SUCH_DEVICE => Some("STATUS_NO_SUCH_DEVICE"), + SMB_NTSTATUS_NO_SUCH_FILE => Some("STATUS_NO_SUCH_FILE"), + SMB_NTSTATUS_INVALID_DEVICE_REQUEST => Some("STATUS_INVALID_DEVICE_REQUEST"), + SMB_NTSTATUS_END_OF_FILE => Some("STATUS_END_OF_FILE"), + SMB_NTSTATUS_WRONG_VOLUME => Some("STATUS_WRONG_VOLUME"), + SMB_NTSTATUS_NO_MEDIA_IN_DEVICE => Some("STATUS_NO_MEDIA_IN_DEVICE"), + SMB_NTSTATUS_UNRECOGNIZED_MEDIA => Some("STATUS_UNRECOGNIZED_MEDIA"), + SMB_NTSTATUS_NONEXISTENT_SECTOR => Some("STATUS_NONEXISTENT_SECTOR"), + SMB_NTSTATUS_MORE_PROCESSING_REQUIRED => Some("STATUS_MORE_PROCESSING_REQUIRED"), + SMB_NTSTATUS_NO_MEMORY => Some("STATUS_NO_MEMORY"), + SMB_NTSTATUS_CONFLICTING_ADDRESSES => Some("STATUS_CONFLICTING_ADDRESSES"), + SMB_NTSTATUS_NOT_MAPPED_VIEW => Some("STATUS_NOT_MAPPED_VIEW"), + SMB_NTSTATUS_UNABLE_TO_FREE_VM => Some("STATUS_UNABLE_TO_FREE_VM"), + SMB_NTSTATUS_UNABLE_TO_DELETE_SECTION => Some("STATUS_UNABLE_TO_DELETE_SECTION"), + SMB_NTSTATUS_INVALID_SYSTEM_SERVICE => Some("STATUS_INVALID_SYSTEM_SERVICE"), + SMB_NTSTATUS_ILLEGAL_INSTRUCTION => Some("STATUS_ILLEGAL_INSTRUCTION"), + SMB_NTSTATUS_INVALID_LOCK_SEQUENCE => Some("STATUS_INVALID_LOCK_SEQUENCE"), + SMB_NTSTATUS_INVALID_VIEW_SIZE => Some("STATUS_INVALID_VIEW_SIZE"), + SMB_NTSTATUS_INVALID_FILE_FOR_SECTION => Some("STATUS_INVALID_FILE_FOR_SECTION"), + SMB_NTSTATUS_ALREADY_COMMITTED => Some("STATUS_ALREADY_COMMITTED"), + SMB_NTSTATUS_ACCESS_DENIED => Some("STATUS_ACCESS_DENIED"), + SMB_NTSTATUS_BUFFER_TOO_SMALL => Some("STATUS_BUFFER_TOO_SMALL"), + SMB_NTSTATUS_OBJECT_TYPE_MISMATCH => Some("STATUS_OBJECT_TYPE_MISMATCH"), + SMB_NTSTATUS_NONCONTINUABLE_EXCEPTION => Some("STATUS_NONCONTINUABLE_EXCEPTION"), + SMB_NTSTATUS_INVALID_DISPOSITION => Some("STATUS_INVALID_DISPOSITION"), + SMB_NTSTATUS_UNWIND => Some("STATUS_UNWIND"), + SMB_NTSTATUS_BAD_STACK => Some("STATUS_BAD_STACK"), + SMB_NTSTATUS_INVALID_UNWIND_TARGET => Some("STATUS_INVALID_UNWIND_TARGET"), + SMB_NTSTATUS_NOT_LOCKED => Some("STATUS_NOT_LOCKED"), + SMB_NTSTATUS_PARITY_ERROR => Some("STATUS_PARITY_ERROR"), + SMB_NTSTATUS_UNABLE_TO_DECOMMIT_VM => Some("STATUS_UNABLE_TO_DECOMMIT_VM"), + SMB_NTSTATUS_NOT_COMMITTED => Some("STATUS_NOT_COMMITTED"), + SMB_NTSTATUS_INVALID_PORT_ATTRIBUTES => Some("STATUS_INVALID_PORT_ATTRIBUTES"), + SMB_NTSTATUS_PORT_MESSAGE_TOO_LONG => Some("STATUS_PORT_MESSAGE_TOO_LONG"), + SMB_NTSTATUS_INVALID_PARAMETER_MIX => Some("STATUS_INVALID_PARAMETER_MIX"), + SMB_NTSTATUS_INVALID_QUOTA_LOWER => Some("STATUS_INVALID_QUOTA_LOWER"), + SMB_NTSTATUS_DISK_CORRUPT_ERROR => Some("STATUS_DISK_CORRUPT_ERROR"), + SMB_NTSTATUS_OBJECT_NAME_INVALID => Some("STATUS_OBJECT_NAME_INVALID"), + SMB_NTSTATUS_OBJECT_NAME_NOT_FOUND => Some("STATUS_OBJECT_NAME_NOT_FOUND"), + SMB_NTSTATUS_OBJECT_NAME_COLLISION => Some("STATUS_OBJECT_NAME_COLLISION"), + SMB_NTSTATUS_PORT_DISCONNECTED => Some("STATUS_PORT_DISCONNECTED"), + SMB_NTSTATUS_DEVICE_ALREADY_ATTACHED => Some("STATUS_DEVICE_ALREADY_ATTACHED"), + SMB_NTSTATUS_OBJECT_PATH_INVALID => Some("STATUS_OBJECT_PATH_INVALID"), + SMB_NTSTATUS_OBJECT_PATH_NOT_FOUND => Some("STATUS_OBJECT_PATH_NOT_FOUND"), + SMB_NTSTATUS_OBJECT_PATH_SYNTAX_BAD => Some("STATUS_OBJECT_PATH_SYNTAX_BAD"), + SMB_NTSTATUS_DATA_OVERRUN => Some("STATUS_DATA_OVERRUN"), + SMB_NTSTATUS_DATA_LATE_ERROR => Some("STATUS_DATA_LATE_ERROR"), + SMB_NTSTATUS_DATA_ERROR => Some("STATUS_DATA_ERROR"), + SMB_NTSTATUS_CRC_ERROR => Some("STATUS_CRC_ERROR"), + SMB_NTSTATUS_SECTION_TOO_BIG => Some("STATUS_SECTION_TOO_BIG"), + SMB_NTSTATUS_PORT_CONNECTION_REFUSED => Some("STATUS_PORT_CONNECTION_REFUSED"), + SMB_NTSTATUS_INVALID_PORT_HANDLE => Some("STATUS_INVALID_PORT_HANDLE"), + SMB_NTSTATUS_SHARING_VIOLATION => Some("STATUS_SHARING_VIOLATION"), + SMB_NTSTATUS_QUOTA_EXCEEDED => Some("STATUS_QUOTA_EXCEEDED"), + SMB_NTSTATUS_INVALID_PAGE_PROTECTION => Some("STATUS_INVALID_PAGE_PROTECTION"), + SMB_NTSTATUS_MUTANT_NOT_OWNED => Some("STATUS_MUTANT_NOT_OWNED"), + SMB_NTSTATUS_SEMAPHORE_LIMIT_EXCEEDED => Some("STATUS_SEMAPHORE_LIMIT_EXCEEDED"), + SMB_NTSTATUS_PORT_ALREADY_SET => Some("STATUS_PORT_ALREADY_SET"), + SMB_NTSTATUS_SECTION_NOT_IMAGE => Some("STATUS_SECTION_NOT_IMAGE"), + SMB_NTSTATUS_SUSPEND_COUNT_EXCEEDED => Some("STATUS_SUSPEND_COUNT_EXCEEDED"), + SMB_NTSTATUS_THREAD_IS_TERMINATING => Some("STATUS_THREAD_IS_TERMINATING"), + SMB_NTSTATUS_BAD_WORKING_SET_LIMIT => Some("STATUS_BAD_WORKING_SET_LIMIT"), + SMB_NTSTATUS_INCOMPATIBLE_FILE_MAP => Some("STATUS_INCOMPATIBLE_FILE_MAP"), + SMB_NTSTATUS_SECTION_PROTECTION => Some("STATUS_SECTION_PROTECTION"), + SMB_NTSTATUS_EAS_NOT_SUPPORTED => Some("STATUS_EAS_NOT_SUPPORTED"), + SMB_NTSTATUS_EA_TOO_LARGE => Some("STATUS_EA_TOO_LARGE"), + SMB_NTSTATUS_NONEXISTENT_EA_ENTRY => Some("STATUS_NONEXISTENT_EA_ENTRY"), + SMB_NTSTATUS_NO_EAS_ON_FILE => Some("STATUS_NO_EAS_ON_FILE"), + SMB_NTSTATUS_EA_CORRUPT_ERROR => Some("STATUS_EA_CORRUPT_ERROR"), + SMB_NTSTATUS_FILE_LOCK_CONFLICT => Some("STATUS_FILE_LOCK_CONFLICT"), + SMB_NTSTATUS_LOCK_NOT_GRANTED => Some("STATUS_LOCK_NOT_GRANTED"), + SMB_NTSTATUS_DELETE_PENDING => Some("STATUS_DELETE_PENDING"), + SMB_NTSTATUS_CTL_FILE_NOT_SUPPORTED => Some("STATUS_CTL_FILE_NOT_SUPPORTED"), + SMB_NTSTATUS_UNKNOWN_REVISION => Some("STATUS_UNKNOWN_REVISION"), + SMB_NTSTATUS_REVISION_MISMATCH => Some("STATUS_REVISION_MISMATCH"), + SMB_NTSTATUS_INVALID_OWNER => Some("STATUS_INVALID_OWNER"), + SMB_NTSTATUS_INVALID_PRIMARY_GROUP => Some("STATUS_INVALID_PRIMARY_GROUP"), + SMB_NTSTATUS_NO_IMPERSONATION_TOKEN => Some("STATUS_NO_IMPERSONATION_TOKEN"), + SMB_NTSTATUS_CANT_DISABLE_MANDATORY => Some("STATUS_CANT_DISABLE_MANDATORY"), + SMB_NTSTATUS_NO_LOGON_SERVERS => Some("STATUS_NO_LOGON_SERVERS"), + SMB_NTSTATUS_NO_SUCH_LOGON_SESSION => Some("STATUS_NO_SUCH_LOGON_SESSION"), + SMB_NTSTATUS_NO_SUCH_PRIVILEGE => Some("STATUS_NO_SUCH_PRIVILEGE"), + SMB_NTSTATUS_PRIVILEGE_NOT_HELD => Some("STATUS_PRIVILEGE_NOT_HELD"), + SMB_NTSTATUS_INVALID_ACCOUNT_NAME => Some("STATUS_INVALID_ACCOUNT_NAME"), + SMB_NTSTATUS_USER_EXISTS => Some("STATUS_USER_EXISTS"), + SMB_NTSTATUS_NO_SUCH_USER => Some("STATUS_NO_SUCH_USER"), + SMB_NTSTATUS_GROUP_EXISTS => Some("STATUS_GROUP_EXISTS"), + SMB_NTSTATUS_NO_SUCH_GROUP => Some("STATUS_NO_SUCH_GROUP"), + SMB_NTSTATUS_MEMBER_IN_GROUP => Some("STATUS_MEMBER_IN_GROUP"), + SMB_NTSTATUS_MEMBER_NOT_IN_GROUP => Some("STATUS_MEMBER_NOT_IN_GROUP"), + SMB_NTSTATUS_LAST_ADMIN => Some("STATUS_LAST_ADMIN"), + SMB_NTSTATUS_WRONG_PASSWORD => Some("STATUS_WRONG_PASSWORD"), + SMB_NTSTATUS_ILL_FORMED_PASSWORD => Some("STATUS_ILL_FORMED_PASSWORD"), + SMB_NTSTATUS_PASSWORD_RESTRICTION => Some("STATUS_PASSWORD_RESTRICTION"), + SMB_NTSTATUS_LOGON_FAILURE => Some("STATUS_LOGON_FAILURE"), + SMB_NTSTATUS_ACCOUNT_RESTRICTION => Some("STATUS_ACCOUNT_RESTRICTION"), + SMB_NTSTATUS_INVALID_LOGON_HOURS => Some("STATUS_INVALID_LOGON_HOURS"), + SMB_NTSTATUS_INVALID_WORKSTATION => Some("STATUS_INVALID_WORKSTATION"), + SMB_NTSTATUS_PASSWORD_EXPIRED => Some("STATUS_PASSWORD_EXPIRED"), + SMB_NTSTATUS_ACCOUNT_DISABLED => Some("STATUS_ACCOUNT_DISABLED"), + SMB_NTSTATUS_NONE_MAPPED => Some("STATUS_NONE_MAPPED"), + SMB_NTSTATUS_TOO_MANY_LUIDS_REQUESTED => Some("STATUS_TOO_MANY_LUIDS_REQUESTED"), + SMB_NTSTATUS_LUIDS_EXHAUSTED => Some("STATUS_LUIDS_EXHAUSTED"), + SMB_NTSTATUS_INVALID_SUB_AUTHORITY => Some("STATUS_INVALID_SUB_AUTHORITY"), + SMB_NTSTATUS_INVALID_ACL => Some("STATUS_INVALID_ACL"), + SMB_NTSTATUS_INVALID_SID => Some("STATUS_INVALID_SID"), + SMB_NTSTATUS_INVALID_SECURITY_DESCR => Some("STATUS_INVALID_SECURITY_DESCR"), + SMB_NTSTATUS_PROCEDURE_NOT_FOUND => Some("STATUS_PROCEDURE_NOT_FOUND"), + SMB_NTSTATUS_INVALID_IMAGE_FORMAT => Some("STATUS_INVALID_IMAGE_FORMAT"), + SMB_NTSTATUS_NO_TOKEN => Some("STATUS_NO_TOKEN"), + SMB_NTSTATUS_BAD_INHERITANCE_ACL => Some("STATUS_BAD_INHERITANCE_ACL"), + SMB_NTSTATUS_RANGE_NOT_LOCKED => Some("STATUS_RANGE_NOT_LOCKED"), + SMB_NTSTATUS_DISK_FULL => Some("STATUS_DISK_FULL"), + SMB_NTSTATUS_SERVER_DISABLED => Some("STATUS_SERVER_DISABLED"), + SMB_NTSTATUS_SERVER_NOT_DISABLED => Some("STATUS_SERVER_NOT_DISABLED"), + SMB_NTSTATUS_TOO_MANY_GUIDS_REQUESTED => Some("STATUS_TOO_MANY_GUIDS_REQUESTED"), + SMB_NTSTATUS_GUIDS_EXHAUSTED => Some("STATUS_GUIDS_EXHAUSTED"), + SMB_NTSTATUS_INVALID_ID_AUTHORITY => Some("STATUS_INVALID_ID_AUTHORITY"), + SMB_NTSTATUS_AGENTS_EXHAUSTED => Some("STATUS_AGENTS_EXHAUSTED"), + SMB_NTSTATUS_INVALID_VOLUME_LABEL => Some("STATUS_INVALID_VOLUME_LABEL"), + SMB_NTSTATUS_SECTION_NOT_EXTENDED => Some("STATUS_SECTION_NOT_EXTENDED"), + SMB_NTSTATUS_NOT_MAPPED_DATA => Some("STATUS_NOT_MAPPED_DATA"), + SMB_NTSTATUS_RESOURCE_DATA_NOT_FOUND => Some("STATUS_RESOURCE_DATA_NOT_FOUND"), + SMB_NTSTATUS_RESOURCE_TYPE_NOT_FOUND => Some("STATUS_RESOURCE_TYPE_NOT_FOUND"), + SMB_NTSTATUS_RESOURCE_NAME_NOT_FOUND => Some("STATUS_RESOURCE_NAME_NOT_FOUND"), + SMB_NTSTATUS_ARRAY_BOUNDS_EXCEEDED => Some("STATUS_ARRAY_BOUNDS_EXCEEDED"), + SMB_NTSTATUS_FLOAT_DENORMAL_OPERAND => Some("STATUS_FLOAT_DENORMAL_OPERAND"), + SMB_NTSTATUS_FLOAT_DIVIDE_BY_ZERO => Some("STATUS_FLOAT_DIVIDE_BY_ZERO"), + SMB_NTSTATUS_FLOAT_INEXACT_RESULT => Some("STATUS_FLOAT_INEXACT_RESULT"), + SMB_NTSTATUS_FLOAT_INVALID_OPERATION => Some("STATUS_FLOAT_INVALID_OPERATION"), + SMB_NTSTATUS_FLOAT_OVERFLOW => Some("STATUS_FLOAT_OVERFLOW"), + SMB_NTSTATUS_FLOAT_STACK_CHECK => Some("STATUS_FLOAT_STACK_CHECK"), + SMB_NTSTATUS_FLOAT_UNDERFLOW => Some("STATUS_FLOAT_UNDERFLOW"), + SMB_NTSTATUS_INTEGER_DIVIDE_BY_ZERO => Some("STATUS_INTEGER_DIVIDE_BY_ZERO"), + SMB_NTSTATUS_INTEGER_OVERFLOW => Some("STATUS_INTEGER_OVERFLOW"), + SMB_NTSTATUS_PRIVILEGED_INSTRUCTION => Some("STATUS_PRIVILEGED_INSTRUCTION"), + SMB_NTSTATUS_TOO_MANY_PAGING_FILES => Some("STATUS_TOO_MANY_PAGING_FILES"), + SMB_NTSTATUS_FILE_INVALID => Some("STATUS_FILE_INVALID"), + SMB_NTSTATUS_ALLOTTED_SPACE_EXCEEDED => Some("STATUS_ALLOTTED_SPACE_EXCEEDED"), + SMB_NTSTATUS_INSUFFICIENT_RESOURCES => Some("STATUS_INSUFFICIENT_RESOURCES"), + SMB_NTSTATUS_DFS_EXIT_PATH_FOUND => Some("STATUS_DFS_EXIT_PATH_FOUND"), + SMB_NTSTATUS_DEVICE_DATA_ERROR => Some("STATUS_DEVICE_DATA_ERROR"), + SMB_NTSTATUS_DEVICE_NOT_CONNECTED => Some("STATUS_DEVICE_NOT_CONNECTED"), + SMB_NTSTATUS_FREE_VM_NOT_AT_BASE => Some("STATUS_FREE_VM_NOT_AT_BASE"), + SMB_NTSTATUS_MEMORY_NOT_ALLOCATED => Some("STATUS_MEMORY_NOT_ALLOCATED"), + SMB_NTSTATUS_WORKING_SET_QUOTA => Some("STATUS_WORKING_SET_QUOTA"), + SMB_NTSTATUS_MEDIA_WRITE_PROTECTED => Some("STATUS_MEDIA_WRITE_PROTECTED"), + SMB_NTSTATUS_DEVICE_NOT_READY => Some("STATUS_DEVICE_NOT_READY"), + SMB_NTSTATUS_INVALID_GROUP_ATTRIBUTES => Some("STATUS_INVALID_GROUP_ATTRIBUTES"), + SMB_NTSTATUS_BAD_IMPERSONATION_LEVEL => Some("STATUS_BAD_IMPERSONATION_LEVEL"), + SMB_NTSTATUS_CANT_OPEN_ANONYMOUS => Some("STATUS_CANT_OPEN_ANONYMOUS"), + SMB_NTSTATUS_BAD_VALIDATION_CLASS => Some("STATUS_BAD_VALIDATION_CLASS"), + SMB_NTSTATUS_BAD_TOKEN_TYPE => Some("STATUS_BAD_TOKEN_TYPE"), + SMB_NTSTATUS_BAD_MASTER_BOOT_RECORD => Some("STATUS_BAD_MASTER_BOOT_RECORD"), + SMB_NTSTATUS_INSTRUCTION_MISALIGNMENT => Some("STATUS_INSTRUCTION_MISALIGNMENT"), + SMB_NTSTATUS_INSTANCE_NOT_AVAILABLE => Some("STATUS_INSTANCE_NOT_AVAILABLE"), + SMB_NTSTATUS_PIPE_NOT_AVAILABLE => Some("STATUS_PIPE_NOT_AVAILABLE"), + SMB_NTSTATUS_INVALID_PIPE_STATE => Some("STATUS_INVALID_PIPE_STATE"), + SMB_NTSTATUS_PIPE_BUSY => Some("STATUS_PIPE_BUSY"), + SMB_NTSTATUS_ILLEGAL_FUNCTION => Some("STATUS_ILLEGAL_FUNCTION"), + SMB_NTSTATUS_PIPE_DISCONNECTED => Some("STATUS_PIPE_DISCONNECTED"), + SMB_NTSTATUS_PIPE_CLOSING => Some("STATUS_PIPE_CLOSING"), + SMB_NTSTATUS_PIPE_CONNECTED => Some("STATUS_PIPE_CONNECTED"), + SMB_NTSTATUS_PIPE_LISTENING => Some("STATUS_PIPE_LISTENING"), + SMB_NTSTATUS_INVALID_READ_MODE => Some("STATUS_INVALID_READ_MODE"), + SMB_NTSTATUS_IO_TIMEOUT => Some("STATUS_IO_TIMEOUT"), + SMB_NTSTATUS_FILE_FORCED_CLOSED => Some("STATUS_FILE_FORCED_CLOSED"), + SMB_NTSTATUS_PROFILING_NOT_STARTED => Some("STATUS_PROFILING_NOT_STARTED"), + SMB_NTSTATUS_PROFILING_NOT_STOPPED => Some("STATUS_PROFILING_NOT_STOPPED"), + SMB_NTSTATUS_COULD_NOT_INTERPRET => Some("STATUS_COULD_NOT_INTERPRET"), + SMB_NTSTATUS_FILE_IS_A_DIRECTORY => Some("STATUS_FILE_IS_A_DIRECTORY"), + SMB_NTSTATUS_NOT_SUPPORTED => Some("STATUS_NOT_SUPPORTED"), + SMB_NTSTATUS_REMOTE_NOT_LISTENING => Some("STATUS_REMOTE_NOT_LISTENING"), + SMB_NTSTATUS_DUPLICATE_NAME => Some("STATUS_DUPLICATE_NAME"), + SMB_NTSTATUS_BAD_NETWORK_PATH => Some("STATUS_BAD_NETWORK_PATH"), + SMB_NTSTATUS_NETWORK_BUSY => Some("STATUS_NETWORK_BUSY"), + SMB_NTSTATUS_DEVICE_DOES_NOT_EXIST => Some("STATUS_DEVICE_DOES_NOT_EXIST"), + SMB_NTSTATUS_TOO_MANY_COMMANDS => Some("STATUS_TOO_MANY_COMMANDS"), + SMB_NTSTATUS_ADAPTER_HARDWARE_ERROR => Some("STATUS_ADAPTER_HARDWARE_ERROR"), + SMB_NTSTATUS_INVALID_NETWORK_RESPONSE => Some("STATUS_INVALID_NETWORK_RESPONSE"), + SMB_NTSTATUS_UNEXPECTED_NETWORK_ERROR => Some("STATUS_UNEXPECTED_NETWORK_ERROR"), + SMB_NTSTATUS_BAD_REMOTE_ADAPTER => Some("STATUS_BAD_REMOTE_ADAPTER"), + SMB_NTSTATUS_PRINT_QUEUE_FULL => Some("STATUS_PRINT_QUEUE_FULL"), + SMB_NTSTATUS_NO_SPOOL_SPACE => Some("STATUS_NO_SPOOL_SPACE"), + SMB_NTSTATUS_PRINT_CANCELLED => Some("STATUS_PRINT_CANCELLED"), + SMB_NTSTATUS_NETWORK_NAME_DELETED => Some("STATUS_NETWORK_NAME_DELETED"), + SMB_NTSTATUS_NETWORK_ACCESS_DENIED => Some("STATUS_NETWORK_ACCESS_DENIED"), + SMB_NTSTATUS_BAD_DEVICE_TYPE => Some("STATUS_BAD_DEVICE_TYPE"), + SMB_NTSTATUS_BAD_NETWORK_NAME => Some("STATUS_BAD_NETWORK_NAME"), + SMB_NTSTATUS_TOO_MANY_NAMES => Some("STATUS_TOO_MANY_NAMES"), + SMB_NTSTATUS_TOO_MANY_SESSIONS => Some("STATUS_TOO_MANY_SESSIONS"), + SMB_NTSTATUS_SHARING_PAUSED => Some("STATUS_SHARING_PAUSED"), + SMB_NTSTATUS_REQUEST_NOT_ACCEPTED => Some("STATUS_REQUEST_NOT_ACCEPTED"), + SMB_NTSTATUS_REDIRECTOR_PAUSED => Some("STATUS_REDIRECTOR_PAUSED"), + SMB_NTSTATUS_NET_WRITE_FAULT => Some("STATUS_NET_WRITE_FAULT"), + SMB_NTSTATUS_PROFILING_AT_LIMIT => Some("STATUS_PROFILING_AT_LIMIT"), + SMB_NTSTATUS_NOT_SAME_DEVICE => Some("STATUS_NOT_SAME_DEVICE"), + SMB_NTSTATUS_FILE_RENAMED => Some("STATUS_FILE_RENAMED"), + SMB_NTSTATUS_VIRTUAL_CIRCUIT_CLOSED => Some("STATUS_VIRTUAL_CIRCUIT_CLOSED"), + SMB_NTSTATUS_NO_SECURITY_ON_OBJECT => Some("STATUS_NO_SECURITY_ON_OBJECT"), + SMB_NTSTATUS_CANT_WAIT => Some("STATUS_CANT_WAIT"), + SMB_NTSTATUS_PIPE_EMPTY => Some("STATUS_PIPE_EMPTY"), + SMB_NTSTATUS_CANT_ACCESS_DOMAIN_INFO => Some("STATUS_CANT_ACCESS_DOMAIN_INFO"), + SMB_NTSTATUS_CANT_TERMINATE_SELF => Some("STATUS_CANT_TERMINATE_SELF"), + SMB_NTSTATUS_INVALID_SERVER_STATE => Some("STATUS_INVALID_SERVER_STATE"), + SMB_NTSTATUS_INVALID_DOMAIN_STATE => Some("STATUS_INVALID_DOMAIN_STATE"), + SMB_NTSTATUS_INVALID_DOMAIN_ROLE => Some("STATUS_INVALID_DOMAIN_ROLE"), + SMB_NTSTATUS_NO_SUCH_DOMAIN => Some("STATUS_NO_SUCH_DOMAIN"), + SMB_NTSTATUS_DOMAIN_EXISTS => Some("STATUS_DOMAIN_EXISTS"), + SMB_NTSTATUS_DOMAIN_LIMIT_EXCEEDED => Some("STATUS_DOMAIN_LIMIT_EXCEEDED"), + SMB_NTSTATUS_OPLOCK_NOT_GRANTED => Some("STATUS_OPLOCK_NOT_GRANTED"), + SMB_NTSTATUS_INVALID_OPLOCK_PROTOCOL => Some("STATUS_INVALID_OPLOCK_PROTOCOL"), + SMB_NTSTATUS_INTERNAL_DB_CORRUPTION => Some("STATUS_INTERNAL_DB_CORRUPTION"), + SMB_NTSTATUS_INTERNAL_ERROR => Some("STATUS_INTERNAL_ERROR"), + SMB_NTSTATUS_GENERIC_NOT_MAPPED => Some("STATUS_GENERIC_NOT_MAPPED"), + SMB_NTSTATUS_BAD_DESCRIPTOR_FORMAT => Some("STATUS_BAD_DESCRIPTOR_FORMAT"), + SMB_NTSTATUS_INVALID_USER_BUFFER => Some("STATUS_INVALID_USER_BUFFER"), + SMB_NTSTATUS_UNEXPECTED_IO_ERROR => Some("STATUS_UNEXPECTED_IO_ERROR"), + SMB_NTSTATUS_UNEXPECTED_MM_CREATE_ERR => Some("STATUS_UNEXPECTED_MM_CREATE_ERR"), + SMB_NTSTATUS_UNEXPECTED_MM_MAP_ERROR => Some("STATUS_UNEXPECTED_MM_MAP_ERROR"), + SMB_NTSTATUS_UNEXPECTED_MM_EXTEND_ERR => Some("STATUS_UNEXPECTED_MM_EXTEND_ERR"), + SMB_NTSTATUS_NOT_LOGON_PROCESS => Some("STATUS_NOT_LOGON_PROCESS"), + SMB_NTSTATUS_LOGON_SESSION_EXISTS => Some("STATUS_LOGON_SESSION_EXISTS"), + SMB_NTSTATUS_INVALID_PARAMETER_1 => Some("STATUS_INVALID_PARAMETER_1"), + SMB_NTSTATUS_INVALID_PARAMETER_2 => Some("STATUS_INVALID_PARAMETER_2"), + SMB_NTSTATUS_INVALID_PARAMETER_3 => Some("STATUS_INVALID_PARAMETER_3"), + SMB_NTSTATUS_INVALID_PARAMETER_4 => Some("STATUS_INVALID_PARAMETER_4"), + SMB_NTSTATUS_INVALID_PARAMETER_5 => Some("STATUS_INVALID_PARAMETER_5"), + SMB_NTSTATUS_INVALID_PARAMETER_6 => Some("STATUS_INVALID_PARAMETER_6"), + SMB_NTSTATUS_INVALID_PARAMETER_7 => Some("STATUS_INVALID_PARAMETER_7"), + SMB_NTSTATUS_INVALID_PARAMETER_8 => Some("STATUS_INVALID_PARAMETER_8"), + SMB_NTSTATUS_INVALID_PARAMETER_9 => Some("STATUS_INVALID_PARAMETER_9"), + SMB_NTSTATUS_INVALID_PARAMETER_10 => Some("STATUS_INVALID_PARAMETER_10"), + SMB_NTSTATUS_INVALID_PARAMETER_11 => Some("STATUS_INVALID_PARAMETER_11"), + SMB_NTSTATUS_INVALID_PARAMETER_12 => Some("STATUS_INVALID_PARAMETER_12"), + SMB_NTSTATUS_REDIRECTOR_NOT_STARTED => Some("STATUS_REDIRECTOR_NOT_STARTED"), + SMB_NTSTATUS_REDIRECTOR_STARTED => Some("STATUS_REDIRECTOR_STARTED"), + SMB_NTSTATUS_STACK_OVERFLOW => Some("STATUS_STACK_OVERFLOW"), + SMB_NTSTATUS_NO_SUCH_PACKAGE => Some("STATUS_NO_SUCH_PACKAGE"), + SMB_NTSTATUS_BAD_FUNCTION_TABLE => Some("STATUS_BAD_FUNCTION_TABLE"), + SMB_NTSTATUS_VARIABLE_NOT_FOUND => Some("STATUS_VARIABLE_NOT_FOUND"), + SMB_NTSTATUS_DIRECTORY_NOT_EMPTY => Some("STATUS_DIRECTORY_NOT_EMPTY"), + SMB_NTSTATUS_FILE_CORRUPT_ERROR => Some("STATUS_FILE_CORRUPT_ERROR"), + SMB_NTSTATUS_NOT_A_DIRECTORY => Some("STATUS_NOT_A_DIRECTORY"), + SMB_NTSTATUS_BAD_LOGON_SESSION_STATE => Some("STATUS_BAD_LOGON_SESSION_STATE"), + SMB_NTSTATUS_LOGON_SESSION_COLLISION => Some("STATUS_LOGON_SESSION_COLLISION"), + SMB_NTSTATUS_NAME_TOO_LONG => Some("STATUS_NAME_TOO_LONG"), + SMB_NTSTATUS_FILES_OPEN => Some("STATUS_FILES_OPEN"), + SMB_NTSTATUS_CONNECTION_IN_USE => Some("STATUS_CONNECTION_IN_USE"), + SMB_NTSTATUS_MESSAGE_NOT_FOUND => Some("STATUS_MESSAGE_NOT_FOUND"), + SMB_NTSTATUS_PROCESS_IS_TERMINATING => Some("STATUS_PROCESS_IS_TERMINATING"), + SMB_NTSTATUS_INVALID_LOGON_TYPE => Some("STATUS_INVALID_LOGON_TYPE"), + SMB_NTSTATUS_NO_GUID_TRANSLATION => Some("STATUS_NO_GUID_TRANSLATION"), + SMB_NTSTATUS_CANNOT_IMPERSONATE => Some("STATUS_CANNOT_IMPERSONATE"), + SMB_NTSTATUS_IMAGE_ALREADY_LOADED => Some("STATUS_IMAGE_ALREADY_LOADED"), + SMB_NTSTATUS_NO_LDT => Some("STATUS_NO_LDT"), + SMB_NTSTATUS_INVALID_LDT_SIZE => Some("STATUS_INVALID_LDT_SIZE"), + SMB_NTSTATUS_INVALID_LDT_OFFSET => Some("STATUS_INVALID_LDT_OFFSET"), + SMB_NTSTATUS_INVALID_LDT_DESCRIPTOR => Some("STATUS_INVALID_LDT_DESCRIPTOR"), + SMB_NTSTATUS_INVALID_IMAGE_NE_FORMAT => Some("STATUS_INVALID_IMAGE_NE_FORMAT"), + SMB_NTSTATUS_RXACT_INVALID_STATE => Some("STATUS_RXACT_INVALID_STATE"), + SMB_NTSTATUS_RXACT_COMMIT_FAILURE => Some("STATUS_RXACT_COMMIT_FAILURE"), + SMB_NTSTATUS_MAPPED_FILE_SIZE_ZERO => Some("STATUS_MAPPED_FILE_SIZE_ZERO"), + SMB_NTSTATUS_TOO_MANY_OPENED_FILES => Some("STATUS_TOO_MANY_OPENED_FILES"), + SMB_NTSTATUS_CANCELLED => Some("STATUS_CANCELLED"), + SMB_NTSTATUS_CANNOT_DELETE => Some("STATUS_CANNOT_DELETE"), + SMB_NTSTATUS_INVALID_COMPUTER_NAME => Some("STATUS_INVALID_COMPUTER_NAME"), + SMB_NTSTATUS_FILE_DELETED => Some("STATUS_FILE_DELETED"), + SMB_NTSTATUS_SPECIAL_ACCOUNT => Some("STATUS_SPECIAL_ACCOUNT"), + SMB_NTSTATUS_SPECIAL_GROUP => Some("STATUS_SPECIAL_GROUP"), + SMB_NTSTATUS_SPECIAL_USER => Some("STATUS_SPECIAL_USER"), + SMB_NTSTATUS_MEMBERS_PRIMARY_GROUP => Some("STATUS_MEMBERS_PRIMARY_GROUP"), + SMB_NTSTATUS_FILE_CLOSED => Some("STATUS_FILE_CLOSED"), + SMB_NTSTATUS_TOO_MANY_THREADS => Some("STATUS_TOO_MANY_THREADS"), + SMB_NTSTATUS_THREAD_NOT_IN_PROCESS => Some("STATUS_THREAD_NOT_IN_PROCESS"), + SMB_NTSTATUS_TOKEN_ALREADY_IN_USE => Some("STATUS_TOKEN_ALREADY_IN_USE"), + SMB_NTSTATUS_PAGEFILE_QUOTA_EXCEEDED => Some("STATUS_PAGEFILE_QUOTA_EXCEEDED"), + SMB_NTSTATUS_COMMITMENT_LIMIT => Some("STATUS_COMMITMENT_LIMIT"), + SMB_NTSTATUS_INVALID_IMAGE_LE_FORMAT => Some("STATUS_INVALID_IMAGE_LE_FORMAT"), + SMB_NTSTATUS_INVALID_IMAGE_NOT_MZ => Some("STATUS_INVALID_IMAGE_NOT_MZ"), + SMB_NTSTATUS_INVALID_IMAGE_PROTECT => Some("STATUS_INVALID_IMAGE_PROTECT"), + SMB_NTSTATUS_INVALID_IMAGE_WIN_16 => Some("STATUS_INVALID_IMAGE_WIN_16"), + SMB_NTSTATUS_LOGON_SERVER_CONFLICT => Some("STATUS_LOGON_SERVER_CONFLICT"), + SMB_NTSTATUS_TIME_DIFFERENCE_AT_DC => Some("STATUS_TIME_DIFFERENCE_AT_DC"), + SMB_NTSTATUS_SYNCHRONIZATION_REQUIRED => Some("STATUS_SYNCHRONIZATION_REQUIRED"), + SMB_NTSTATUS_DLL_NOT_FOUND => Some("STATUS_DLL_NOT_FOUND"), + SMB_NTSTATUS_OPEN_FAILED => Some("STATUS_OPEN_FAILED"), + SMB_NTSTATUS_IO_PRIVILEGE_FAILED => Some("STATUS_IO_PRIVILEGE_FAILED"), + SMB_NTSTATUS_ORDINAL_NOT_FOUND => Some("STATUS_ORDINAL_NOT_FOUND"), + SMB_NTSTATUS_ENTRYPOINT_NOT_FOUND => Some("STATUS_ENTRYPOINT_NOT_FOUND"), + SMB_NTSTATUS_CONTROL_C_EXIT => Some("STATUS_CONTROL_C_EXIT"), + SMB_NTSTATUS_LOCAL_DISCONNECT => Some("STATUS_LOCAL_DISCONNECT"), + SMB_NTSTATUS_REMOTE_DISCONNECT => Some("STATUS_REMOTE_DISCONNECT"), + SMB_NTSTATUS_REMOTE_RESOURCES => Some("STATUS_REMOTE_RESOURCES"), + SMB_NTSTATUS_LINK_FAILED => Some("STATUS_LINK_FAILED"), + SMB_NTSTATUS_LINK_TIMEOUT => Some("STATUS_LINK_TIMEOUT"), + SMB_NTSTATUS_INVALID_CONNECTION => Some("STATUS_INVALID_CONNECTION"), + SMB_NTSTATUS_INVALID_ADDRESS => Some("STATUS_INVALID_ADDRESS"), + SMB_NTSTATUS_DLL_INIT_FAILED => Some("STATUS_DLL_INIT_FAILED"), + SMB_NTSTATUS_MISSING_SYSTEMFILE => Some("STATUS_MISSING_SYSTEMFILE"), + SMB_NTSTATUS_UNHANDLED_EXCEPTION => Some("STATUS_UNHANDLED_EXCEPTION"), + SMB_NTSTATUS_APP_INIT_FAILURE => Some("STATUS_APP_INIT_FAILURE"), + SMB_NTSTATUS_PAGEFILE_CREATE_FAILED => Some("STATUS_PAGEFILE_CREATE_FAILED"), + SMB_NTSTATUS_NO_PAGEFILE => Some("STATUS_NO_PAGEFILE"), + SMB_NTSTATUS_INVALID_LEVEL => Some("STATUS_INVALID_LEVEL"), + SMB_NTSTATUS_WRONG_PASSWORD_CORE => Some("STATUS_WRONG_PASSWORD_CORE"), + SMB_NTSTATUS_ILLEGAL_FLOAT_CONTEXT => Some("STATUS_ILLEGAL_FLOAT_CONTEXT"), + SMB_NTSTATUS_PIPE_BROKEN => Some("STATUS_PIPE_BROKEN"), + SMB_NTSTATUS_REGISTRY_CORRUPT => Some("STATUS_REGISTRY_CORRUPT"), + SMB_NTSTATUS_REGISTRY_IO_FAILED => Some("STATUS_REGISTRY_IO_FAILED"), + SMB_NTSTATUS_NO_EVENT_PAIR => Some("STATUS_NO_EVENT_PAIR"), + SMB_NTSTATUS_UNRECOGNIZED_VOLUME => Some("STATUS_UNRECOGNIZED_VOLUME"), + SMB_NTSTATUS_SERIAL_NO_DEVICE_INITED => Some("STATUS_SERIAL_NO_DEVICE_INITED"), + SMB_NTSTATUS_NO_SUCH_ALIAS => Some("STATUS_NO_SUCH_ALIAS"), + SMB_NTSTATUS_MEMBER_NOT_IN_ALIAS => Some("STATUS_MEMBER_NOT_IN_ALIAS"), + SMB_NTSTATUS_MEMBER_IN_ALIAS => Some("STATUS_MEMBER_IN_ALIAS"), + SMB_NTSTATUS_ALIAS_EXISTS => Some("STATUS_ALIAS_EXISTS"), + SMB_NTSTATUS_LOGON_NOT_GRANTED => Some("STATUS_LOGON_NOT_GRANTED"), + SMB_NTSTATUS_TOO_MANY_SECRETS => Some("STATUS_TOO_MANY_SECRETS"), + SMB_NTSTATUS_SECRET_TOO_LONG => Some("STATUS_SECRET_TOO_LONG"), + SMB_NTSTATUS_INTERNAL_DB_ERROR => Some("STATUS_INTERNAL_DB_ERROR"), + SMB_NTSTATUS_FULLSCREEN_MODE => Some("STATUS_FULLSCREEN_MODE"), + SMB_NTSTATUS_TOO_MANY_CONTEXT_IDS => Some("STATUS_TOO_MANY_CONTEXT_IDS"), + SMB_NTSTATUS_LOGON_TYPE_NOT_GRANTED => Some("STATUS_LOGON_TYPE_NOT_GRANTED"), + SMB_NTSTATUS_NOT_REGISTRY_FILE => Some("STATUS_NOT_REGISTRY_FILE"), + SMB_NTSTATUS_NT_CROSS_ENCRYPTION_REQUIRED => Some("STATUS_NT_CROSS_ENCRYPTION_REQUIRED"), + SMB_NTSTATUS_DOMAIN_CTRLR_CONFIG_ERROR => Some("STATUS_DOMAIN_CTRLR_CONFIG_ERROR"), + SMB_NTSTATUS_FT_MISSING_MEMBER => Some("STATUS_FT_MISSING_MEMBER"), + SMB_NTSTATUS_ILL_FORMED_SERVICE_ENTRY => Some("STATUS_ILL_FORMED_SERVICE_ENTRY"), + SMB_NTSTATUS_ILLEGAL_CHARACTER => Some("STATUS_ILLEGAL_CHARACTER"), + SMB_NTSTATUS_UNMAPPABLE_CHARACTER => Some("STATUS_UNMAPPABLE_CHARACTER"), + SMB_NTSTATUS_UNDEFINED_CHARACTER => Some("STATUS_UNDEFINED_CHARACTER"), + SMB_NTSTATUS_FLOPPY_VOLUME => Some("STATUS_FLOPPY_VOLUME"), + SMB_NTSTATUS_FLOPPY_ID_MARK_NOT_FOUND => Some("STATUS_FLOPPY_ID_MARK_NOT_FOUND"), + SMB_NTSTATUS_FLOPPY_WRONG_CYLINDER => Some("STATUS_FLOPPY_WRONG_CYLINDER"), + SMB_NTSTATUS_FLOPPY_UNKNOWN_ERROR => Some("STATUS_FLOPPY_UNKNOWN_ERROR"), + SMB_NTSTATUS_FLOPPY_BAD_REGISTERS => Some("STATUS_FLOPPY_BAD_REGISTERS"), + SMB_NTSTATUS_DISK_RECALIBRATE_FAILED => Some("STATUS_DISK_RECALIBRATE_FAILED"), + SMB_NTSTATUS_DISK_OPERATION_FAILED => Some("STATUS_DISK_OPERATION_FAILED"), + SMB_NTSTATUS_DISK_RESET_FAILED => Some("STATUS_DISK_RESET_FAILED"), + SMB_NTSTATUS_SHARED_IRQ_BUSY => Some("STATUS_SHARED_IRQ_BUSY"), + SMB_NTSTATUS_FT_ORPHANING => Some("STATUS_FT_ORPHANING"), + SMB_NTSTATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT => Some("STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT"), + SMB_NTSTATUS_PARTITION_FAILURE => Some("STATUS_PARTITION_FAILURE"), + SMB_NTSTATUS_INVALID_BLOCK_LENGTH => Some("STATUS_INVALID_BLOCK_LENGTH"), + SMB_NTSTATUS_DEVICE_NOT_PARTITIONED => Some("STATUS_DEVICE_NOT_PARTITIONED"), + SMB_NTSTATUS_UNABLE_TO_LOCK_MEDIA => Some("STATUS_UNABLE_TO_LOCK_MEDIA"), + SMB_NTSTATUS_UNABLE_TO_UNLOAD_MEDIA => Some("STATUS_UNABLE_TO_UNLOAD_MEDIA"), + SMB_NTSTATUS_EOM_OVERFLOW => Some("STATUS_EOM_OVERFLOW"), + SMB_NTSTATUS_NO_MEDIA => Some("STATUS_NO_MEDIA"), + SMB_NTSTATUS_NO_SUCH_MEMBER => Some("STATUS_NO_SUCH_MEMBER"), + SMB_NTSTATUS_INVALID_MEMBER => Some("STATUS_INVALID_MEMBER"), + SMB_NTSTATUS_KEY_DELETED => Some("STATUS_KEY_DELETED"), + SMB_NTSTATUS_NO_LOG_SPACE => Some("STATUS_NO_LOG_SPACE"), + SMB_NTSTATUS_TOO_MANY_SIDS => Some("STATUS_TOO_MANY_SIDS"), + SMB_NTSTATUS_LM_CROSS_ENCRYPTION_REQUIRED => Some("STATUS_LM_CROSS_ENCRYPTION_REQUIRED"), + SMB_NTSTATUS_KEY_HAS_CHILDREN => Some("STATUS_KEY_HAS_CHILDREN"), + SMB_NTSTATUS_CHILD_MUST_BE_VOLATILE => Some("STATUS_CHILD_MUST_BE_VOLATILE"), + SMB_NTSTATUS_DEVICE_CONFIGURATION_ERROR => Some("STATUS_DEVICE_CONFIGURATION_ERROR"), + SMB_NTSTATUS_DRIVER_INTERNAL_ERROR => Some("STATUS_DRIVER_INTERNAL_ERROR"), + SMB_NTSTATUS_INVALID_DEVICE_STATE => Some("STATUS_INVALID_DEVICE_STATE"), + SMB_NTSTATUS_IO_DEVICE_ERROR => Some("STATUS_IO_DEVICE_ERROR"), + SMB_NTSTATUS_DEVICE_PROTOCOL_ERROR => Some("STATUS_DEVICE_PROTOCOL_ERROR"), + SMB_NTSTATUS_BACKUP_CONTROLLER => Some("STATUS_BACKUP_CONTROLLER"), + SMB_NTSTATUS_LOG_FILE_FULL => Some("STATUS_LOG_FILE_FULL"), + SMB_NTSTATUS_TOO_LATE => Some("STATUS_TOO_LATE"), + SMB_NTSTATUS_NO_TRUST_LSA_SECRET => Some("STATUS_NO_TRUST_LSA_SECRET"), + SMB_NTSTATUS_NO_TRUST_SAM_ACCOUNT => Some("STATUS_NO_TRUST_SAM_ACCOUNT"), + SMB_NTSTATUS_TRUSTED_DOMAIN_FAILURE => Some("STATUS_TRUSTED_DOMAIN_FAILURE"), + SMB_NTSTATUS_TRUSTED_RELATIONSHIP_FAILURE => Some("STATUS_TRUSTED_RELATIONSHIP_FAILURE"), + SMB_NTSTATUS_EVENTLOG_FILE_CORRUPT => Some("STATUS_EVENTLOG_FILE_CORRUPT"), + SMB_NTSTATUS_EVENTLOG_CANT_START => Some("STATUS_EVENTLOG_CANT_START"), + SMB_NTSTATUS_TRUST_FAILURE => Some("STATUS_TRUST_FAILURE"), + SMB_NTSTATUS_MUTANT_LIMIT_EXCEEDED => Some("STATUS_MUTANT_LIMIT_EXCEEDED"), + SMB_NTSTATUS_NETLOGON_NOT_STARTED => Some("STATUS_NETLOGON_NOT_STARTED"), + SMB_NTSTATUS_ACCOUNT_EXPIRED => Some("STATUS_ACCOUNT_EXPIRED"), + SMB_NTSTATUS_POSSIBLE_DEADLOCK => Some("STATUS_POSSIBLE_DEADLOCK"), + SMB_NTSTATUS_NETWORK_CREDENTIAL_CONFLICT => Some("STATUS_NETWORK_CREDENTIAL_CONFLICT"), + SMB_NTSTATUS_REMOTE_SESSION_LIMIT => Some("STATUS_REMOTE_SESSION_LIMIT"), + SMB_NTSTATUS_EVENTLOG_FILE_CHANGED => Some("STATUS_EVENTLOG_FILE_CHANGED"), + SMB_NTSTATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT => Some("STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT"), + SMB_NTSTATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT => Some("STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT"), + SMB_NTSTATUS_NOLOGON_SERVER_TRUST_ACCOUNT => Some("STATUS_NOLOGON_SERVER_TRUST_ACCOUNT"), + SMB_NTSTATUS_DOMAIN_TRUST_INCONSISTENT => Some("STATUS_DOMAIN_TRUST_INCONSISTENT"), + SMB_NTSTATUS_FS_DRIVER_REQUIRED => Some("STATUS_FS_DRIVER_REQUIRED"), + SMB_NTSTATUS_IMAGE_ALREADY_LOADED_AS_DLL => Some("STATUS_IMAGE_ALREADY_LOADED_AS_DLL"), + SMB_NTSTATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING => Some("STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING"), + SMB_NTSTATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME => Some("STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME"), + SMB_NTSTATUS_SECURITY_STREAM_IS_INCONSISTENT => Some("STATUS_SECURITY_STREAM_IS_INCONSISTENT"), + SMB_NTSTATUS_INVALID_LOCK_RANGE => Some("STATUS_INVALID_LOCK_RANGE"), + SMB_NTSTATUS_INVALID_ACE_CONDITION => Some("STATUS_INVALID_ACE_CONDITION"), + SMB_NTSTATUS_IMAGE_SUBSYSTEM_NOT_PRESENT => Some("STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT"), + SMB_NTSTATUS_NOTIFICATION_GUID_ALREADY_DEFINED => Some("STATUS_NOTIFICATION_GUID_ALREADY_DEFINED"), + SMB_NTSTATUS_NETWORK_OPEN_RESTRICTION => Some("STATUS_NETWORK_OPEN_RESTRICTION"), + SMB_NTSTATUS_NO_USER_SESSION_KEY => Some("STATUS_NO_USER_SESSION_KEY"), + SMB_NTSTATUS_USER_SESSION_DELETED => Some("STATUS_USER_SESSION_DELETED"), + SMB_NTSTATUS_RESOURCE_LANG_NOT_FOUND => Some("STATUS_RESOURCE_LANG_NOT_FOUND"), + SMB_NTSTATUS_INSUFF_SERVER_RESOURCES => Some("STATUS_INSUFF_SERVER_RESOURCES"), + SMB_NTSTATUS_INVALID_BUFFER_SIZE => Some("STATUS_INVALID_BUFFER_SIZE"), + SMB_NTSTATUS_INVALID_ADDRESS_COMPONENT => Some("STATUS_INVALID_ADDRESS_COMPONENT"), + SMB_NTSTATUS_INVALID_ADDRESS_WILDCARD => Some("STATUS_INVALID_ADDRESS_WILDCARD"), + SMB_NTSTATUS_TOO_MANY_ADDRESSES => Some("STATUS_TOO_MANY_ADDRESSES"), + SMB_NTSTATUS_ADDRESS_ALREADY_EXISTS => Some("STATUS_ADDRESS_ALREADY_EXISTS"), + SMB_NTSTATUS_ADDRESS_CLOSED => Some("STATUS_ADDRESS_CLOSED"), + SMB_NTSTATUS_CONNECTION_DISCONNECTED => Some("STATUS_CONNECTION_DISCONNECTED"), + SMB_NTSTATUS_CONNECTION_RESET => Some("STATUS_CONNECTION_RESET"), + SMB_NTSTATUS_TOO_MANY_NODES => Some("STATUS_TOO_MANY_NODES"), + SMB_NTSTATUS_TRANSACTION_ABORTED => Some("STATUS_TRANSACTION_ABORTED"), + SMB_NTSTATUS_TRANSACTION_TIMED_OUT => Some("STATUS_TRANSACTION_TIMED_OUT"), + SMB_NTSTATUS_TRANSACTION_NO_RELEASE => Some("STATUS_TRANSACTION_NO_RELEASE"), + SMB_NTSTATUS_TRANSACTION_NO_MATCH => Some("STATUS_TRANSACTION_NO_MATCH"), + SMB_NTSTATUS_TRANSACTION_RESPONDED => Some("STATUS_TRANSACTION_RESPONDED"), + SMB_NTSTATUS_TRANSACTION_INVALID_ID => Some("STATUS_TRANSACTION_INVALID_ID"), + SMB_NTSTATUS_TRANSACTION_INVALID_TYPE => Some("STATUS_TRANSACTION_INVALID_TYPE"), + SMB_NTSTATUS_NOT_SERVER_SESSION => Some("STATUS_NOT_SERVER_SESSION"), + SMB_NTSTATUS_NOT_CLIENT_SESSION => Some("STATUS_NOT_CLIENT_SESSION"), + SMB_NTSTATUS_CANNOT_LOAD_REGISTRY_FILE => Some("STATUS_CANNOT_LOAD_REGISTRY_FILE"), + SMB_NTSTATUS_DEBUG_ATTACH_FAILED => Some("STATUS_DEBUG_ATTACH_FAILED"), + SMB_NTSTATUS_SYSTEM_PROCESS_TERMINATED => Some("STATUS_SYSTEM_PROCESS_TERMINATED"), + SMB_NTSTATUS_DATA_NOT_ACCEPTED => Some("STATUS_DATA_NOT_ACCEPTED"), + SMB_NTSTATUS_NO_BROWSER_SERVERS_FOUND => Some("STATUS_NO_BROWSER_SERVERS_FOUND"), + SMB_NTSTATUS_VDM_HARD_ERROR => Some("STATUS_VDM_HARD_ERROR"), + SMB_NTSTATUS_DRIVER_CANCEL_TIMEOUT => Some("STATUS_DRIVER_CANCEL_TIMEOUT"), + SMB_NTSTATUS_REPLY_MESSAGE_MISMATCH => Some("STATUS_REPLY_MESSAGE_MISMATCH"), + SMB_NTSTATUS_MAPPED_ALIGNMENT => Some("STATUS_MAPPED_ALIGNMENT"), + SMB_NTSTATUS_IMAGE_CHECKSUM_MISMATCH => Some("STATUS_IMAGE_CHECKSUM_MISMATCH"), + SMB_NTSTATUS_LOST_WRITEBEHIND_DATA => Some("STATUS_LOST_WRITEBEHIND_DATA"), + SMB_NTSTATUS_CLIENT_SERVER_PARAMETERS_INVALID => Some("STATUS_CLIENT_SERVER_PARAMETERS_INVALID"), + SMB_NTSTATUS_PASSWORD_MUST_CHANGE => Some("STATUS_PASSWORD_MUST_CHANGE"), + SMB_NTSTATUS_NOT_FOUND => Some("STATUS_NOT_FOUND"), + SMB_NTSTATUS_NOT_TINY_STREAM => Some("STATUS_NOT_TINY_STREAM"), + SMB_NTSTATUS_RECOVERY_FAILURE => Some("STATUS_RECOVERY_FAILURE"), + SMB_NTSTATUS_STACK_OVERFLOW_READ => Some("STATUS_STACK_OVERFLOW_READ"), + SMB_NTSTATUS_FAIL_CHECK => Some("STATUS_FAIL_CHECK"), + SMB_NTSTATUS_DUPLICATE_OBJECTID => Some("STATUS_DUPLICATE_OBJECTID"), + SMB_NTSTATUS_OBJECTID_EXISTS => Some("STATUS_OBJECTID_EXISTS"), + SMB_NTSTATUS_CONVERT_TO_LARGE => Some("STATUS_CONVERT_TO_LARGE"), + SMB_NTSTATUS_RETRY => Some("STATUS_RETRY"), + SMB_NTSTATUS_FOUND_OUT_OF_SCOPE => Some("STATUS_FOUND_OUT_OF_SCOPE"), + SMB_NTSTATUS_ALLOCATE_BUCKET => Some("STATUS_ALLOCATE_BUCKET"), + SMB_NTSTATUS_PROPSET_NOT_FOUND => Some("STATUS_PROPSET_NOT_FOUND"), + SMB_NTSTATUS_MARSHALL_OVERFLOW => Some("STATUS_MARSHALL_OVERFLOW"), + SMB_NTSTATUS_INVALID_VARIANT => Some("STATUS_INVALID_VARIANT"), + SMB_NTSTATUS_DOMAIN_CONTROLLER_NOT_FOUND => Some("STATUS_DOMAIN_CONTROLLER_NOT_FOUND"), + SMB_NTSTATUS_ACCOUNT_LOCKED_OUT => Some("STATUS_ACCOUNT_LOCKED_OUT"), + SMB_NTSTATUS_HANDLE_NOT_CLOSABLE => Some("STATUS_HANDLE_NOT_CLOSABLE"), + SMB_NTSTATUS_CONNECTION_REFUSED => Some("STATUS_CONNECTION_REFUSED"), + SMB_NTSTATUS_GRACEFUL_DISCONNECT => Some("STATUS_GRACEFUL_DISCONNECT"), + SMB_NTSTATUS_ADDRESS_ALREADY_ASSOCIATED => Some("STATUS_ADDRESS_ALREADY_ASSOCIATED"), + SMB_NTSTATUS_ADDRESS_NOT_ASSOCIATED => Some("STATUS_ADDRESS_NOT_ASSOCIATED"), + SMB_NTSTATUS_CONNECTION_INVALID => Some("STATUS_CONNECTION_INVALID"), + SMB_NTSTATUS_CONNECTION_ACTIVE => Some("STATUS_CONNECTION_ACTIVE"), + SMB_NTSTATUS_NETWORK_UNREACHABLE => Some("STATUS_NETWORK_UNREACHABLE"), + SMB_NTSTATUS_HOST_UNREACHABLE => Some("STATUS_HOST_UNREACHABLE"), + SMB_NTSTATUS_PROTOCOL_UNREACHABLE => Some("STATUS_PROTOCOL_UNREACHABLE"), + SMB_NTSTATUS_PORT_UNREACHABLE => Some("STATUS_PORT_UNREACHABLE"), + SMB_NTSTATUS_REQUEST_ABORTED => Some("STATUS_REQUEST_ABORTED"), + SMB_NTSTATUS_CONNECTION_ABORTED => Some("STATUS_CONNECTION_ABORTED"), + SMB_NTSTATUS_BAD_COMPRESSION_BUFFER => Some("STATUS_BAD_COMPRESSION_BUFFER"), + SMB_NTSTATUS_USER_MAPPED_FILE => Some("STATUS_USER_MAPPED_FILE"), + SMB_NTSTATUS_AUDIT_FAILED => Some("STATUS_AUDIT_FAILED"), + SMB_NTSTATUS_TIMER_RESOLUTION_NOT_SET => Some("STATUS_TIMER_RESOLUTION_NOT_SET"), + SMB_NTSTATUS_CONNECTION_COUNT_LIMIT => Some("STATUS_CONNECTION_COUNT_LIMIT"), + SMB_NTSTATUS_LOGIN_TIME_RESTRICTION => Some("STATUS_LOGIN_TIME_RESTRICTION"), + SMB_NTSTATUS_LOGIN_WKSTA_RESTRICTION => Some("STATUS_LOGIN_WKSTA_RESTRICTION"), + SMB_NTSTATUS_IMAGE_MP_UP_MISMATCH => Some("STATUS_IMAGE_MP_UP_MISMATCH"), + SMB_NTSTATUS_INSUFFICIENT_LOGON_INFO => Some("STATUS_INSUFFICIENT_LOGON_INFO"), + SMB_NTSTATUS_BAD_DLL_ENTRYPOINT => Some("STATUS_BAD_DLL_ENTRYPOINT"), + SMB_NTSTATUS_BAD_SERVICE_ENTRYPOINT => Some("STATUS_BAD_SERVICE_ENTRYPOINT"), + SMB_NTSTATUS_LPC_REPLY_LOST => Some("STATUS_LPC_REPLY_LOST"), + SMB_NTSTATUS_IP_ADDRESS_CONFLICT1 => Some("STATUS_IP_ADDRESS_CONFLICT1"), + SMB_NTSTATUS_IP_ADDRESS_CONFLICT2 => Some("STATUS_IP_ADDRESS_CONFLICT2"), + SMB_NTSTATUS_REGISTRY_QUOTA_LIMIT => Some("STATUS_REGISTRY_QUOTA_LIMIT"), + SMB_NTSTATUS_PATH_NOT_COVERED => Some("STATUS_PATH_NOT_COVERED"), + SMB_NTSTATUS_NO_CALLBACK_ACTIVE => Some("STATUS_NO_CALLBACK_ACTIVE"), + SMB_NTSTATUS_LICENSE_QUOTA_EXCEEDED => Some("STATUS_LICENSE_QUOTA_EXCEEDED"), + SMB_NTSTATUS_PWD_TOO_SHORT => Some("STATUS_PWD_TOO_SHORT"), + SMB_NTSTATUS_PWD_TOO_RECENT => Some("STATUS_PWD_TOO_RECENT"), + SMB_NTSTATUS_PWD_HISTORY_CONFLICT => Some("STATUS_PWD_HISTORY_CONFLICT"), + SMB_NTSTATUS_PLUGPLAY_NO_DEVICE => Some("STATUS_PLUGPLAY_NO_DEVICE"), + SMB_NTSTATUS_UNSUPPORTED_COMPRESSION => Some("STATUS_UNSUPPORTED_COMPRESSION"), + SMB_NTSTATUS_INVALID_HW_PROFILE => Some("STATUS_INVALID_HW_PROFILE"), + SMB_NTSTATUS_INVALID_PLUGPLAY_DEVICE_PATH => Some("STATUS_INVALID_PLUGPLAY_DEVICE_PATH"), + SMB_NTSTATUS_DRIVER_ORDINAL_NOT_FOUND => Some("STATUS_DRIVER_ORDINAL_NOT_FOUND"), + SMB_NTSTATUS_DRIVER_ENTRYPOINT_NOT_FOUND => Some("STATUS_DRIVER_ENTRYPOINT_NOT_FOUND"), + SMB_NTSTATUS_RESOURCE_NOT_OWNED => Some("STATUS_RESOURCE_NOT_OWNED"), + SMB_NTSTATUS_TOO_MANY_LINKS => Some("STATUS_TOO_MANY_LINKS"), + SMB_NTSTATUS_QUOTA_LIST_INCONSISTENT => Some("STATUS_QUOTA_LIST_INCONSISTENT"), + SMB_NTSTATUS_FILE_IS_OFFLINE => Some("STATUS_FILE_IS_OFFLINE"), + SMB_NTSTATUS_EVALUATION_EXPIRATION => Some("STATUS_EVALUATION_EXPIRATION"), + SMB_NTSTATUS_ILLEGAL_DLL_RELOCATION => Some("STATUS_ILLEGAL_DLL_RELOCATION"), + SMB_NTSTATUS_LICENSE_VIOLATION => Some("STATUS_LICENSE_VIOLATION"), + SMB_NTSTATUS_DLL_INIT_FAILED_LOGOFF => Some("STATUS_DLL_INIT_FAILED_LOGOFF"), + SMB_NTSTATUS_DRIVER_UNABLE_TO_LOAD => Some("STATUS_DRIVER_UNABLE_TO_LOAD"), + SMB_NTSTATUS_DFS_UNAVAILABLE => Some("STATUS_DFS_UNAVAILABLE"), + SMB_NTSTATUS_VOLUME_DISMOUNTED => Some("STATUS_VOLUME_DISMOUNTED"), + SMB_NTSTATUS_WX86_INTERNAL_ERROR => Some("STATUS_WX86_INTERNAL_ERROR"), + SMB_NTSTATUS_WX86_FLOAT_STACK_CHECK => Some("STATUS_WX86_FLOAT_STACK_CHECK"), + SMB_NTSTATUS_VALIDATE_CONTINUE => Some("STATUS_VALIDATE_CONTINUE"), + SMB_NTSTATUS_NO_MATCH => Some("STATUS_NO_MATCH"), + SMB_NTSTATUS_NO_MORE_MATCHES => Some("STATUS_NO_MORE_MATCHES"), + SMB_NTSTATUS_NOT_A_REPARSE_POINT => Some("STATUS_NOT_A_REPARSE_POINT"), + SMB_NTSTATUS_IO_REPARSE_TAG_INVALID => Some("STATUS_IO_REPARSE_TAG_INVALID"), + SMB_NTSTATUS_IO_REPARSE_TAG_MISMATCH => Some("STATUS_IO_REPARSE_TAG_MISMATCH"), + SMB_NTSTATUS_IO_REPARSE_DATA_INVALID => Some("STATUS_IO_REPARSE_DATA_INVALID"), + SMB_NTSTATUS_IO_REPARSE_TAG_NOT_HANDLED => Some("STATUS_IO_REPARSE_TAG_NOT_HANDLED"), + SMB_NTSTATUS_REPARSE_POINT_NOT_RESOLVED => Some("STATUS_REPARSE_POINT_NOT_RESOLVED"), + SMB_NTSTATUS_DIRECTORY_IS_A_REPARSE_POINT => Some("STATUS_DIRECTORY_IS_A_REPARSE_POINT"), + SMB_NTSTATUS_RANGE_LIST_CONFLICT => Some("STATUS_RANGE_LIST_CONFLICT"), + SMB_NTSTATUS_SOURCE_ELEMENT_EMPTY => Some("STATUS_SOURCE_ELEMENT_EMPTY"), + SMB_NTSTATUS_DESTINATION_ELEMENT_FULL => Some("STATUS_DESTINATION_ELEMENT_FULL"), + SMB_NTSTATUS_ILLEGAL_ELEMENT_ADDRESS => Some("STATUS_ILLEGAL_ELEMENT_ADDRESS"), + SMB_NTSTATUS_MAGAZINE_NOT_PRESENT => Some("STATUS_MAGAZINE_NOT_PRESENT"), + SMB_NTSTATUS_REINITIALIZATION_NEEDED => Some("STATUS_REINITIALIZATION_NEEDED"), + SMB_NTSTATUS_ENCRYPTION_FAILED => Some("STATUS_ENCRYPTION_FAILED"), + SMB_NTSTATUS_DECRYPTION_FAILED => Some("STATUS_DECRYPTION_FAILED"), + SMB_NTSTATUS_RANGE_NOT_FOUND => Some("STATUS_RANGE_NOT_FOUND"), + SMB_NTSTATUS_NO_RECOVERY_POLICY => Some("STATUS_NO_RECOVERY_POLICY"), + SMB_NTSTATUS_NO_EFS => Some("STATUS_NO_EFS"), + SMB_NTSTATUS_WRONG_EFS => Some("STATUS_WRONG_EFS"), + SMB_NTSTATUS_NO_USER_KEYS => Some("STATUS_NO_USER_KEYS"), + SMB_NTSTATUS_FILE_NOT_ENCRYPTED => Some("STATUS_FILE_NOT_ENCRYPTED"), + SMB_NTSTATUS_NOT_EXPORT_FORMAT => Some("STATUS_NOT_EXPORT_FORMAT"), + SMB_NTSTATUS_FILE_ENCRYPTED => Some("STATUS_FILE_ENCRYPTED"), + SMB_NTSTATUS_WMI_GUID_NOT_FOUND => Some("STATUS_WMI_GUID_NOT_FOUND"), + SMB_NTSTATUS_WMI_INSTANCE_NOT_FOUND => Some("STATUS_WMI_INSTANCE_NOT_FOUND"), + SMB_NTSTATUS_WMI_ITEMID_NOT_FOUND => Some("STATUS_WMI_ITEMID_NOT_FOUND"), + SMB_NTSTATUS_WMI_TRY_AGAIN => Some("STATUS_WMI_TRY_AGAIN"), + SMB_NTSTATUS_SHARED_POLICY => Some("STATUS_SHARED_POLICY"), + SMB_NTSTATUS_POLICY_OBJECT_NOT_FOUND => Some("STATUS_POLICY_OBJECT_NOT_FOUND"), + SMB_NTSTATUS_POLICY_ONLY_IN_DS => Some("STATUS_POLICY_ONLY_IN_DS"), + SMB_NTSTATUS_VOLUME_NOT_UPGRADED => Some("STATUS_VOLUME_NOT_UPGRADED"), + SMB_NTSTATUS_REMOTE_STORAGE_NOT_ACTIVE => Some("STATUS_REMOTE_STORAGE_NOT_ACTIVE"), + SMB_NTSTATUS_REMOTE_STORAGE_MEDIA_ERROR => Some("STATUS_REMOTE_STORAGE_MEDIA_ERROR"), + SMB_NTSTATUS_NO_TRACKING_SERVICE => Some("STATUS_NO_TRACKING_SERVICE"), + SMB_NTSTATUS_SERVER_SID_MISMATCH => Some("STATUS_SERVER_SID_MISMATCH"), + SMB_NTSTATUS_DS_NO_ATTRIBUTE_OR_VALUE => Some("STATUS_DS_NO_ATTRIBUTE_OR_VALUE"), + SMB_NTSTATUS_DS_INVALID_ATTRIBUTE_SYNTAX => Some("STATUS_DS_INVALID_ATTRIBUTE_SYNTAX"), + SMB_NTSTATUS_DS_ATTRIBUTE_TYPE_UNDEFINED => Some("STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED"), + SMB_NTSTATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS => Some("STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS"), + SMB_NTSTATUS_DS_BUSY => Some("STATUS_DS_BUSY"), + SMB_NTSTATUS_DS_UNAVAILABLE => Some("STATUS_DS_UNAVAILABLE"), + SMB_NTSTATUS_DS_NO_RIDS_ALLOCATED => Some("STATUS_DS_NO_RIDS_ALLOCATED"), + SMB_NTSTATUS_DS_NO_MORE_RIDS => Some("STATUS_DS_NO_MORE_RIDS"), + SMB_NTSTATUS_DS_INCORRECT_ROLE_OWNER => Some("STATUS_DS_INCORRECT_ROLE_OWNER"), + SMB_NTSTATUS_DS_RIDMGR_INIT_ERROR => Some("STATUS_DS_RIDMGR_INIT_ERROR"), + SMB_NTSTATUS_DS_OBJ_CLASS_VIOLATION => Some("STATUS_DS_OBJ_CLASS_VIOLATION"), + SMB_NTSTATUS_DS_CANT_ON_NON_LEAF => Some("STATUS_DS_CANT_ON_NON_LEAF"), + SMB_NTSTATUS_DS_CANT_ON_RDN => Some("STATUS_DS_CANT_ON_RDN"), + SMB_NTSTATUS_DS_CANT_MOD_OBJ_CLASS => Some("STATUS_DS_CANT_MOD_OBJ_CLASS"), + SMB_NTSTATUS_DS_CROSS_DOM_MOVE_FAILED => Some("STATUS_DS_CROSS_DOM_MOVE_FAILED"), + SMB_NTSTATUS_DS_GC_NOT_AVAILABLE => Some("STATUS_DS_GC_NOT_AVAILABLE"), + SMB_NTSTATUS_DIRECTORY_SERVICE_REQUIRED => Some("STATUS_DIRECTORY_SERVICE_REQUIRED"), + SMB_NTSTATUS_REPARSE_ATTRIBUTE_CONFLICT => Some("STATUS_REPARSE_ATTRIBUTE_CONFLICT"), + SMB_NTSTATUS_CANT_ENABLE_DENY_ONLY => Some("STATUS_CANT_ENABLE_DENY_ONLY"), + SMB_NTSTATUS_FLOAT_MULTIPLE_FAULTS => Some("STATUS_FLOAT_MULTIPLE_FAULTS"), + SMB_NTSTATUS_FLOAT_MULTIPLE_TRAPS => Some("STATUS_FLOAT_MULTIPLE_TRAPS"), + SMB_NTSTATUS_DEVICE_REMOVED => Some("STATUS_DEVICE_REMOVED"), + SMB_NTSTATUS_JOURNAL_DELETE_IN_PROGRESS => Some("STATUS_JOURNAL_DELETE_IN_PROGRESS"), + SMB_NTSTATUS_JOURNAL_NOT_ACTIVE => Some("STATUS_JOURNAL_NOT_ACTIVE"), + SMB_NTSTATUS_NOINTERFACE => Some("STATUS_NOINTERFACE"), + SMB_NTSTATUS_DS_ADMIN_LIMIT_EXCEEDED => Some("STATUS_DS_ADMIN_LIMIT_EXCEEDED"), + SMB_NTSTATUS_DRIVER_FAILED_SLEEP => Some("STATUS_DRIVER_FAILED_SLEEP"), + SMB_NTSTATUS_MUTUAL_AUTHENTICATION_FAILED => Some("STATUS_MUTUAL_AUTHENTICATION_FAILED"), + SMB_NTSTATUS_CORRUPT_SYSTEM_FILE => Some("STATUS_CORRUPT_SYSTEM_FILE"), + SMB_NTSTATUS_DATATYPE_MISALIGNMENT_ERROR => Some("STATUS_DATATYPE_MISALIGNMENT_ERROR"), + SMB_NTSTATUS_WMI_READ_ONLY => Some("STATUS_WMI_READ_ONLY"), + SMB_NTSTATUS_WMI_SET_FAILURE => Some("STATUS_WMI_SET_FAILURE"), + SMB_NTSTATUS_COMMITMENT_MINIMUM => Some("STATUS_COMMITMENT_MINIMUM"), + SMB_NTSTATUS_REG_NAT_CONSUMPTION => Some("STATUS_REG_NAT_CONSUMPTION"), + SMB_NTSTATUS_TRANSPORT_FULL => Some("STATUS_TRANSPORT_FULL"), + SMB_NTSTATUS_DS_SAM_INIT_FAILURE => Some("STATUS_DS_SAM_INIT_FAILURE"), + SMB_NTSTATUS_ONLY_IF_CONNECTED => Some("STATUS_ONLY_IF_CONNECTED"), + SMB_NTSTATUS_DS_SENSITIVE_GROUP_VIOLATION => Some("STATUS_DS_SENSITIVE_GROUP_VIOLATION"), + SMB_NTSTATUS_PNP_RESTART_ENUMERATION => Some("STATUS_PNP_RESTART_ENUMERATION"), + SMB_NTSTATUS_JOURNAL_ENTRY_DELETED => Some("STATUS_JOURNAL_ENTRY_DELETED"), + SMB_NTSTATUS_DS_CANT_MOD_PRIMARYGROUPID => Some("STATUS_DS_CANT_MOD_PRIMARYGROUPID"), + SMB_NTSTATUS_SYSTEM_IMAGE_BAD_SIGNATURE => Some("STATUS_SYSTEM_IMAGE_BAD_SIGNATURE"), + SMB_NTSTATUS_PNP_REBOOT_REQUIRED => Some("STATUS_PNP_REBOOT_REQUIRED"), + SMB_NTSTATUS_POWER_STATE_INVALID => Some("STATUS_POWER_STATE_INVALID"), + SMB_NTSTATUS_DS_INVALID_GROUP_TYPE => Some("STATUS_DS_INVALID_GROUP_TYPE"), + SMB_NTSTATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN => Some("STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN"), + SMB_NTSTATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN => Some("STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN"), + SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER => Some("STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER"), + SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER => Some("STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER"), + SMB_NTSTATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER => Some("STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER"), + SMB_NTSTATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER => Some("STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER"), + SMB_NTSTATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER => Some("STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER"), + SMB_NTSTATUS_DS_HAVE_PRIMARY_MEMBERS => Some("STATUS_DS_HAVE_PRIMARY_MEMBERS"), + SMB_NTSTATUS_WMI_NOT_SUPPORTED => Some("STATUS_WMI_NOT_SUPPORTED"), + SMB_NTSTATUS_INSUFFICIENT_POWER => Some("STATUS_INSUFFICIENT_POWER"), + SMB_NTSTATUS_SAM_NEED_BOOTKEY_PASSWORD => Some("STATUS_SAM_NEED_BOOTKEY_PASSWORD"), + SMB_NTSTATUS_SAM_NEED_BOOTKEY_FLOPPY => Some("STATUS_SAM_NEED_BOOTKEY_FLOPPY"), + SMB_NTSTATUS_DS_CANT_START => Some("STATUS_DS_CANT_START"), + SMB_NTSTATUS_DS_INIT_FAILURE => Some("STATUS_DS_INIT_FAILURE"), + SMB_NTSTATUS_SAM_INIT_FAILURE => Some("STATUS_SAM_INIT_FAILURE"), + SMB_NTSTATUS_DS_GC_REQUIRED => Some("STATUS_DS_GC_REQUIRED"), + SMB_NTSTATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY => Some("STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY"), + SMB_NTSTATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS => Some("STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS"), + SMB_NTSTATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED => Some("STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED"), + SMB_NTSTATUS_CURRENT_DOMAIN_NOT_ALLOWED => Some("STATUS_CURRENT_DOMAIN_NOT_ALLOWED"), + SMB_NTSTATUS_CANNOT_MAKE => Some("STATUS_CANNOT_MAKE"), + SMB_NTSTATUS_SYSTEM_SHUTDOWN => Some("STATUS_SYSTEM_SHUTDOWN"), + SMB_NTSTATUS_DS_INIT_FAILURE_CONSOLE => Some("STATUS_DS_INIT_FAILURE_CONSOLE"), + SMB_NTSTATUS_DS_SAM_INIT_FAILURE_CONSOLE => Some("STATUS_DS_SAM_INIT_FAILURE_CONSOLE"), + SMB_NTSTATUS_UNFINISHED_CONTEXT_DELETED => Some("STATUS_UNFINISHED_CONTEXT_DELETED"), + SMB_NTSTATUS_NO_TGT_REPLY => Some("STATUS_NO_TGT_REPLY"), + SMB_NTSTATUS_OBJECTID_NOT_FOUND => Some("STATUS_OBJECTID_NOT_FOUND"), + SMB_NTSTATUS_NO_IP_ADDRESSES => Some("STATUS_NO_IP_ADDRESSES"), + SMB_NTSTATUS_WRONG_CREDENTIAL_HANDLE => Some("STATUS_WRONG_CREDENTIAL_HANDLE"), + SMB_NTSTATUS_CRYPTO_SYSTEM_INVALID => Some("STATUS_CRYPTO_SYSTEM_INVALID"), + SMB_NTSTATUS_MAX_REFERRALS_EXCEEDED => Some("STATUS_MAX_REFERRALS_EXCEEDED"), + SMB_NTSTATUS_MUST_BE_KDC => Some("STATUS_MUST_BE_KDC"), + SMB_NTSTATUS_STRONG_CRYPTO_NOT_SUPPORTED => Some("STATUS_STRONG_CRYPTO_NOT_SUPPORTED"), + SMB_NTSTATUS_TOO_MANY_PRINCIPALS => Some("STATUS_TOO_MANY_PRINCIPALS"), + SMB_NTSTATUS_NO_PA_DATA => Some("STATUS_NO_PA_DATA"), + SMB_NTSTATUS_PKINIT_NAME_MISMATCH => Some("STATUS_PKINIT_NAME_MISMATCH"), + SMB_NTSTATUS_SMARTCARD_LOGON_REQUIRED => Some("STATUS_SMARTCARD_LOGON_REQUIRED"), + SMB_NTSTATUS_KDC_INVALID_REQUEST => Some("STATUS_KDC_INVALID_REQUEST"), + SMB_NTSTATUS_KDC_UNABLE_TO_REFER => Some("STATUS_KDC_UNABLE_TO_REFER"), + SMB_NTSTATUS_KDC_UNKNOWN_ETYPE => Some("STATUS_KDC_UNKNOWN_ETYPE"), + SMB_NTSTATUS_SHUTDOWN_IN_PROGRESS => Some("STATUS_SHUTDOWN_IN_PROGRESS"), + SMB_NTSTATUS_SERVER_SHUTDOWN_IN_PROGRESS => Some("STATUS_SERVER_SHUTDOWN_IN_PROGRESS"), + SMB_NTSTATUS_NOT_SUPPORTED_ON_SBS => Some("STATUS_NOT_SUPPORTED_ON_SBS"), + SMB_NTSTATUS_WMI_GUID_DISCONNECTED => Some("STATUS_WMI_GUID_DISCONNECTED"), + SMB_NTSTATUS_WMI_ALREADY_DISABLED => Some("STATUS_WMI_ALREADY_DISABLED"), + SMB_NTSTATUS_WMI_ALREADY_ENABLED => Some("STATUS_WMI_ALREADY_ENABLED"), + SMB_NTSTATUS_MFT_TOO_FRAGMENTED => Some("STATUS_MFT_TOO_FRAGMENTED"), + SMB_NTSTATUS_COPY_PROTECTION_FAILURE => Some("STATUS_COPY_PROTECTION_FAILURE"), + SMB_NTSTATUS_CSS_AUTHENTICATION_FAILURE => Some("STATUS_CSS_AUTHENTICATION_FAILURE"), + SMB_NTSTATUS_CSS_KEY_NOT_PRESENT => Some("STATUS_CSS_KEY_NOT_PRESENT"), + SMB_NTSTATUS_CSS_KEY_NOT_ESTABLISHED => Some("STATUS_CSS_KEY_NOT_ESTABLISHED"), + SMB_NTSTATUS_CSS_SCRAMBLED_SECTOR => Some("STATUS_CSS_SCRAMBLED_SECTOR"), + SMB_NTSTATUS_CSS_REGION_MISMATCH => Some("STATUS_CSS_REGION_MISMATCH"), + SMB_NTSTATUS_CSS_RESETS_EXHAUSTED => Some("STATUS_CSS_RESETS_EXHAUSTED"), + SMB_NTSTATUS_PKINIT_FAILURE => Some("STATUS_PKINIT_FAILURE"), + SMB_NTSTATUS_SMARTCARD_SUBSYSTEM_FAILURE => Some("STATUS_SMARTCARD_SUBSYSTEM_FAILURE"), + SMB_NTSTATUS_NO_KERB_KEY => Some("STATUS_NO_KERB_KEY"), + SMB_NTSTATUS_HOST_DOWN => Some("STATUS_HOST_DOWN"), + SMB_NTSTATUS_UNSUPPORTED_PREAUTH => Some("STATUS_UNSUPPORTED_PREAUTH"), + SMB_NTSTATUS_EFS_ALG_BLOB_TOO_BIG => Some("STATUS_EFS_ALG_BLOB_TOO_BIG"), + SMB_NTSTATUS_PORT_NOT_SET => Some("STATUS_PORT_NOT_SET"), + SMB_NTSTATUS_DEBUGGER_INACTIVE => Some("STATUS_DEBUGGER_INACTIVE"), + SMB_NTSTATUS_DS_VERSION_CHECK_FAILURE => Some("STATUS_DS_VERSION_CHECK_FAILURE"), + SMB_NTSTATUS_AUDITING_DISABLED => Some("STATUS_AUDITING_DISABLED"), + SMB_NTSTATUS_PRENT4_MACHINE_ACCOUNT => Some("STATUS_PRENT4_MACHINE_ACCOUNT"), + SMB_NTSTATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER => Some("STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER"), + SMB_NTSTATUS_INVALID_IMAGE_WIN_32 => Some("STATUS_INVALID_IMAGE_WIN_32"), + SMB_NTSTATUS_INVALID_IMAGE_WIN_64 => Some("STATUS_INVALID_IMAGE_WIN_64"), + SMB_NTSTATUS_BAD_BINDINGS => Some("STATUS_BAD_BINDINGS"), + SMB_NTSTATUS_NETWORK_SESSION_EXPIRED => Some("STATUS_NETWORK_SESSION_EXPIRED"), + SMB_NTSTATUS_APPHELP_BLOCK => Some("STATUS_APPHELP_BLOCK"), + SMB_NTSTATUS_ALL_SIDS_FILTERED => Some("STATUS_ALL_SIDS_FILTERED"), + SMB_NTSTATUS_NOT_SAFE_MODE_DRIVER => Some("STATUS_NOT_SAFE_MODE_DRIVER"), + SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT => Some("STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT"), + SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_PATH => Some("STATUS_ACCESS_DISABLED_BY_POLICY_PATH"), + SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER => Some("STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER"), + SMB_NTSTATUS_ACCESS_DISABLED_BY_POLICY_OTHER => Some("STATUS_ACCESS_DISABLED_BY_POLICY_OTHER"), + SMB_NTSTATUS_FAILED_DRIVER_ENTRY => Some("STATUS_FAILED_DRIVER_ENTRY"), + SMB_NTSTATUS_DEVICE_ENUMERATION_ERROR => Some("STATUS_DEVICE_ENUMERATION_ERROR"), + SMB_NTSTATUS_MOUNT_POINT_NOT_RESOLVED => Some("STATUS_MOUNT_POINT_NOT_RESOLVED"), + SMB_NTSTATUS_INVALID_DEVICE_OBJECT_PARAMETER => Some("STATUS_INVALID_DEVICE_OBJECT_PARAMETER"), + SMB_NTSTATUS_MCA_OCCURED => Some("STATUS_MCA_OCCURED"), + SMB_NTSTATUS_DRIVER_BLOCKED_CRITICAL => Some("STATUS_DRIVER_BLOCKED_CRITICAL"), + SMB_NTSTATUS_DRIVER_BLOCKED => Some("STATUS_DRIVER_BLOCKED"), + SMB_NTSTATUS_DRIVER_DATABASE_ERROR => Some("STATUS_DRIVER_DATABASE_ERROR"), + SMB_NTSTATUS_SYSTEM_HIVE_TOO_LARGE => Some("STATUS_SYSTEM_HIVE_TOO_LARGE"), + SMB_NTSTATUS_INVALID_IMPORT_OF_NON_DLL => Some("STATUS_INVALID_IMPORT_OF_NON_DLL"), + SMB_NTSTATUS_NO_SECRETS => Some("STATUS_NO_SECRETS"), + SMB_NTSTATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY => Some("STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY"), + SMB_NTSTATUS_FAILED_STACK_SWITCH => Some("STATUS_FAILED_STACK_SWITCH"), + SMB_NTSTATUS_HEAP_CORRUPTION => Some("STATUS_HEAP_CORRUPTION"), + SMB_NTSTATUS_SMARTCARD_WRONG_PIN => Some("STATUS_SMARTCARD_WRONG_PIN"), + SMB_NTSTATUS_SMARTCARD_CARD_BLOCKED => Some("STATUS_SMARTCARD_CARD_BLOCKED"), + SMB_NTSTATUS_SMARTCARD_CARD_NOT_AUTHENTICATED => Some("STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED"), + SMB_NTSTATUS_SMARTCARD_NO_CARD => Some("STATUS_SMARTCARD_NO_CARD"), + SMB_NTSTATUS_SMARTCARD_NO_KEY_CONTAINER => Some("STATUS_SMARTCARD_NO_KEY_CONTAINER"), + SMB_NTSTATUS_SMARTCARD_NO_CERTIFICATE => Some("STATUS_SMARTCARD_NO_CERTIFICATE"), + SMB_NTSTATUS_SMARTCARD_NO_KEYSET => Some("STATUS_SMARTCARD_NO_KEYSET"), + SMB_NTSTATUS_SMARTCARD_IO_ERROR => Some("STATUS_SMARTCARD_IO_ERROR"), + SMB_NTSTATUS_DOWNGRADE_DETECTED => Some("STATUS_DOWNGRADE_DETECTED"), + SMB_NTSTATUS_SMARTCARD_CERT_REVOKED => Some("STATUS_SMARTCARD_CERT_REVOKED"), + SMB_NTSTATUS_ISSUING_CA_UNTRUSTED => Some("STATUS_ISSUING_CA_UNTRUSTED"), + SMB_NTSTATUS_REVOCATION_OFFLINE_C => Some("STATUS_REVOCATION_OFFLINE_C"), + SMB_NTSTATUS_PKINIT_CLIENT_FAILURE => Some("STATUS_PKINIT_CLIENT_FAILURE"), + SMB_NTSTATUS_SMARTCARD_CERT_EXPIRED => Some("STATUS_SMARTCARD_CERT_EXPIRED"), + SMB_NTSTATUS_DRIVER_FAILED_PRIOR_UNLOAD => Some("STATUS_DRIVER_FAILED_PRIOR_UNLOAD"), + SMB_NTSTATUS_SMARTCARD_SILENT_CONTEXT => Some("STATUS_SMARTCARD_SILENT_CONTEXT"), + SMB_NTSTATUS_PER_USER_TRUST_QUOTA_EXCEEDED => Some("STATUS_PER_USER_TRUST_QUOTA_EXCEEDED"), + SMB_NTSTATUS_ALL_USER_TRUST_QUOTA_EXCEEDED => Some("STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED"), + SMB_NTSTATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED => Some("STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED"), + SMB_NTSTATUS_DS_NAME_NOT_UNIQUE => Some("STATUS_DS_NAME_NOT_UNIQUE"), + SMB_NTSTATUS_DS_DUPLICATE_ID_FOUND => Some("STATUS_DS_DUPLICATE_ID_FOUND"), + SMB_NTSTATUS_DS_GROUP_CONVERSION_ERROR => Some("STATUS_DS_GROUP_CONVERSION_ERROR"), + SMB_NTSTATUS_VOLSNAP_PREPARE_HIBERNATE => Some("STATUS_VOLSNAP_PREPARE_HIBERNATE"), + SMB_NTSTATUS_USER2USER_REQUIRED => Some("STATUS_USER2USER_REQUIRED"), + SMB_NTSTATUS_STACK_BUFFER_OVERRUN => Some("STATUS_STACK_BUFFER_OVERRUN"), + SMB_NTSTATUS_NO_S4U_PROT_SUPPORT => Some("STATUS_NO_S4U_PROT_SUPPORT"), + SMB_NTSTATUS_CROSSREALM_DELEGATION_FAILURE => Some("STATUS_CROSSREALM_DELEGATION_FAILURE"), + SMB_NTSTATUS_REVOCATION_OFFLINE_KDC => Some("STATUS_REVOCATION_OFFLINE_KDC"), + SMB_NTSTATUS_ISSUING_CA_UNTRUSTED_KDC => Some("STATUS_ISSUING_CA_UNTRUSTED_KDC"), + SMB_NTSTATUS_KDC_CERT_EXPIRED => Some("STATUS_KDC_CERT_EXPIRED"), + SMB_NTSTATUS_KDC_CERT_REVOKED => Some("STATUS_KDC_CERT_REVOKED"), + SMB_NTSTATUS_PARAMETER_QUOTA_EXCEEDED => Some("STATUS_PARAMETER_QUOTA_EXCEEDED"), + SMB_NTSTATUS_HIBERNATION_FAILURE => Some("STATUS_HIBERNATION_FAILURE"), + SMB_NTSTATUS_DELAY_LOAD_FAILED => Some("STATUS_DELAY_LOAD_FAILED"), + SMB_NTSTATUS_AUTHENTICATION_FIREWALL_FAILED => Some("STATUS_AUTHENTICATION_FIREWALL_FAILED"), + SMB_NTSTATUS_VDM_DISALLOWED => Some("STATUS_VDM_DISALLOWED"), + SMB_NTSTATUS_HUNG_DISPLAY_DRIVER_THREAD => Some("STATUS_HUNG_DISPLAY_DRIVER_THREAD"), + SMB_NTSTATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE => Some("STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE"), + SMB_NTSTATUS_INVALID_CRUNTIME_PARAMETER => Some("STATUS_INVALID_CRUNTIME_PARAMETER"), + SMB_NTSTATUS_NTLM_BLOCKED => Some("STATUS_NTLM_BLOCKED"), + SMB_NTSTATUS_DS_SRC_SID_EXISTS_IN_FOREST => Some("STATUS_DS_SRC_SID_EXISTS_IN_FOREST"), + SMB_NTSTATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST => Some("STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST"), + SMB_NTSTATUS_DS_FLAT_NAME_EXISTS_IN_FOREST => Some("STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST"), + SMB_NTSTATUS_INVALID_USER_PRINCIPAL_NAME => Some("STATUS_INVALID_USER_PRINCIPAL_NAME"), + SMB_NTSTATUS_ASSERTION_FAILURE => Some("STATUS_ASSERTION_FAILURE"), + SMB_NTSTATUS_VERIFIER_STOP => Some("STATUS_VERIFIER_STOP"), + SMB_NTSTATUS_CALLBACK_POP_STACK => Some("STATUS_CALLBACK_POP_STACK"), + SMB_NTSTATUS_INCOMPATIBLE_DRIVER_BLOCKED => Some("STATUS_INCOMPATIBLE_DRIVER_BLOCKED"), + SMB_NTSTATUS_HIVE_UNLOADED => Some("STATUS_HIVE_UNLOADED"), + SMB_NTSTATUS_COMPRESSION_DISABLED => Some("STATUS_COMPRESSION_DISABLED"), + SMB_NTSTATUS_FILE_SYSTEM_LIMITATION => Some("STATUS_FILE_SYSTEM_LIMITATION"), + SMB_NTSTATUS_INVALID_IMAGE_HASH => Some("STATUS_INVALID_IMAGE_HASH"), + SMB_NTSTATUS_NOT_CAPABLE => Some("STATUS_NOT_CAPABLE"), + SMB_NTSTATUS_REQUEST_OUT_OF_SEQUENCE => Some("STATUS_REQUEST_OUT_OF_SEQUENCE"), + SMB_NTSTATUS_IMPLEMENTATION_LIMIT => Some("STATUS_IMPLEMENTATION_LIMIT"), + SMB_NTSTATUS_ELEVATION_REQUIRED => Some("STATUS_ELEVATION_REQUIRED"), + SMB_NTSTATUS_NO_SECURITY_CONTEXT => Some("STATUS_NO_SECURITY_CONTEXT"), + SMB_NTSTATUS_PKU2U_CERT_FAILURE => Some("STATUS_PKU2U_CERT_FAILURE"), + SMB_NTSTATUS_BEYOND_VDL => Some("STATUS_BEYOND_VDL"), + SMB_NTSTATUS_ENCOUNTERED_WRITE_IN_PROGRESS => Some("STATUS_ENCOUNTERED_WRITE_IN_PROGRESS"), + SMB_NTSTATUS_PTE_CHANGED => Some("STATUS_PTE_CHANGED"), + SMB_NTSTATUS_PURGE_FAILED => Some("STATUS_PURGE_FAILED"), + SMB_NTSTATUS_CRED_REQUIRES_CONFIRMATION => Some("STATUS_CRED_REQUIRES_CONFIRMATION"), + SMB_NTSTATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE => Some("STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE"), + SMB_NTSTATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER => Some("STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER"), + SMB_NTSTATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE => Some("STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE"), + SMB_NTSTATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE => Some("STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE"), + SMB_NTSTATUS_CS_ENCRYPTION_FILE_NOT_CSE => Some("STATUS_CS_ENCRYPTION_FILE_NOT_CSE"), + SMB_NTSTATUS_INVALID_LABEL => Some("STATUS_INVALID_LABEL"), + SMB_NTSTATUS_DRIVER_PROCESS_TERMINATED => Some("STATUS_DRIVER_PROCESS_TERMINATED"), + SMB_NTSTATUS_AMBIGUOUS_SYSTEM_DEVICE => Some("STATUS_AMBIGUOUS_SYSTEM_DEVICE"), + SMB_NTSTATUS_SYSTEM_DEVICE_NOT_FOUND => Some("STATUS_SYSTEM_DEVICE_NOT_FOUND"), + SMB_NTSTATUS_RESTART_BOOT_APPLICATION => Some("STATUS_RESTART_BOOT_APPLICATION"), + SMB_NTSTATUS_INSUFFICIENT_NVRAM_RESOURCES => Some("STATUS_INSUFFICIENT_NVRAM_RESOURCES"), + SMB_NTSTATUS_NO_RANGES_PROCESSED => Some("STATUS_NO_RANGES_PROCESSED"), + SMB_NTSTATUS_DEVICE_FEATURE_NOT_SUPPORTED => Some("STATUS_DEVICE_FEATURE_NOT_SUPPORTED"), + SMB_NTSTATUS_DEVICE_UNREACHABLE => Some("STATUS_DEVICE_UNREACHABLE"), + SMB_NTSTATUS_INVALID_TOKEN => Some("STATUS_INVALID_TOKEN"), + SMB_NTSTATUS_SERVER_UNAVAILABLE => Some("STATUS_SERVER_UNAVAILABLE"), + SMB_NTSTATUS_INVALID_TASK_NAME => Some("STATUS_INVALID_TASK_NAME"), + SMB_NTSTATUS_INVALID_TASK_INDEX => Some("STATUS_INVALID_TASK_INDEX"), + SMB_NTSTATUS_THREAD_ALREADY_IN_TASK => Some("STATUS_THREAD_ALREADY_IN_TASK"), + SMB_NTSTATUS_CALLBACK_BYPASS => Some("STATUS_CALLBACK_BYPASS"), + SMB_NTSTATUS_FAIL_FAST_EXCEPTION => Some("STATUS_FAIL_FAST_EXCEPTION"), + SMB_NTSTATUS_IMAGE_CERT_REVOKED => Some("STATUS_IMAGE_CERT_REVOKED"), + SMB_NTSTATUS_PORT_CLOSED => Some("STATUS_PORT_CLOSED"), + SMB_NTSTATUS_MESSAGE_LOST => Some("STATUS_MESSAGE_LOST"), + SMB_NTSTATUS_INVALID_MESSAGE => Some("STATUS_INVALID_MESSAGE"), + SMB_NTSTATUS_REQUEST_CANCELED => Some("STATUS_REQUEST_CANCELED"), + SMB_NTSTATUS_RECURSIVE_DISPATCH => Some("STATUS_RECURSIVE_DISPATCH"), + SMB_NTSTATUS_LPC_RECEIVE_BUFFER_EXPECTED => Some("STATUS_LPC_RECEIVE_BUFFER_EXPECTED"), + SMB_NTSTATUS_LPC_INVALID_CONNECTION_USAGE => Some("STATUS_LPC_INVALID_CONNECTION_USAGE"), + SMB_NTSTATUS_LPC_REQUESTS_NOT_ALLOWED => Some("STATUS_LPC_REQUESTS_NOT_ALLOWED"), + SMB_NTSTATUS_RESOURCE_IN_USE => Some("STATUS_RESOURCE_IN_USE"), + SMB_NTSTATUS_HARDWARE_MEMORY_ERROR => Some("STATUS_HARDWARE_MEMORY_ERROR"), + SMB_NTSTATUS_THREADPOOL_HANDLE_EXCEPTION => Some("STATUS_THREADPOOL_HANDLE_EXCEPTION"), + SMB_NTSTATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED => Some("STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED"), + SMB_NTSTATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED => Some("STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED"), + SMB_NTSTATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED => Some("STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED"), + SMB_NTSTATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED => Some("STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED"), + SMB_NTSTATUS_THREADPOOL_RELEASED_DURING_OPERATION => Some("STATUS_THREADPOOL_RELEASED_DURING_OPERATION"), + SMB_NTSTATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING => Some("STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING"), + SMB_NTSTATUS_APC_RETURNED_WHILE_IMPERSONATING => Some("STATUS_APC_RETURNED_WHILE_IMPERSONATING"), + SMB_NTSTATUS_PROCESS_IS_PROTECTED => Some("STATUS_PROCESS_IS_PROTECTED"), + SMB_NTSTATUS_MCA_EXCEPTION => Some("STATUS_MCA_EXCEPTION"), + SMB_NTSTATUS_CERTIFICATE_MAPPING_NOT_UNIQUE => Some("STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE"), + SMB_NTSTATUS_SYMLINK_CLASS_DISABLED => Some("STATUS_SYMLINK_CLASS_DISABLED"), + SMB_NTSTATUS_INVALID_IDN_NORMALIZATION => Some("STATUS_INVALID_IDN_NORMALIZATION"), + SMB_NTSTATUS_NO_UNICODE_TRANSLATION => Some("STATUS_NO_UNICODE_TRANSLATION"), + SMB_NTSTATUS_ALREADY_REGISTERED => Some("STATUS_ALREADY_REGISTERED"), + SMB_NTSTATUS_CONTEXT_MISMATCH => Some("STATUS_CONTEXT_MISMATCH"), + SMB_NTSTATUS_PORT_ALREADY_HAS_COMPLETION_LIST => Some("STATUS_PORT_ALREADY_HAS_COMPLETION_LIST"), + SMB_NTSTATUS_CALLBACK_RETURNED_THREAD_PRIORITY => Some("STATUS_CALLBACK_RETURNED_THREAD_PRIORITY"), + SMB_NTSTATUS_INVALID_THREAD => Some("STATUS_INVALID_THREAD"), + SMB_NTSTATUS_CALLBACK_RETURNED_TRANSACTION => Some("STATUS_CALLBACK_RETURNED_TRANSACTION"), + SMB_NTSTATUS_CALLBACK_RETURNED_LDR_LOCK => Some("STATUS_CALLBACK_RETURNED_LDR_LOCK"), + SMB_NTSTATUS_CALLBACK_RETURNED_LANG => Some("STATUS_CALLBACK_RETURNED_LANG"), + SMB_NTSTATUS_CALLBACK_RETURNED_PRI_BACK => Some("STATUS_CALLBACK_RETURNED_PRI_BACK"), + SMB_NTSTATUS_DISK_REPAIR_DISABLED => Some("STATUS_DISK_REPAIR_DISABLED"), + SMB_NTSTATUS_DS_DOMAIN_RENAME_IN_PROGRESS => Some("STATUS_DS_DOMAIN_RENAME_IN_PROGRESS"), + SMB_NTSTATUS_DISK_QUOTA_EXCEEDED => Some("STATUS_DISK_QUOTA_EXCEEDED"), + SMB_NTSTATUS_CONTENT_BLOCKED => Some("STATUS_CONTENT_BLOCKED"), + SMB_NTSTATUS_BAD_CLUSTERS => Some("STATUS_BAD_CLUSTERS"), + SMB_NTSTATUS_VOLUME_DIRTY => Some("STATUS_VOLUME_DIRTY"), + SMB_NTSTATUS_FILE_CHECKED_OUT => Some("STATUS_FILE_CHECKED_OUT"), + SMB_NTSTATUS_CHECKOUT_REQUIRED => Some("STATUS_CHECKOUT_REQUIRED"), + SMB_NTSTATUS_BAD_FILE_TYPE => Some("STATUS_BAD_FILE_TYPE"), + SMB_NTSTATUS_FILE_TOO_LARGE => Some("STATUS_FILE_TOO_LARGE"), + SMB_NTSTATUS_FORMS_AUTH_REQUIRED => Some("STATUS_FORMS_AUTH_REQUIRED"), + SMB_NTSTATUS_VIRUS_INFECTED => Some("STATUS_VIRUS_INFECTED"), + SMB_NTSTATUS_VIRUS_DELETED => Some("STATUS_VIRUS_DELETED"), + SMB_NTSTATUS_BAD_MCFG_TABLE => Some("STATUS_BAD_MCFG_TABLE"), + SMB_NTSTATUS_CANNOT_BREAK_OPLOCK => Some("STATUS_CANNOT_BREAK_OPLOCK"), + SMB_NTSTATUS_WOW_ASSERTION => Some("STATUS_WOW_ASSERTION"), + SMB_NTSTATUS_INVALID_SIGNATURE => Some("STATUS_INVALID_SIGNATURE"), + SMB_NTSTATUS_HMAC_NOT_SUPPORTED => Some("STATUS_HMAC_NOT_SUPPORTED"), + SMB_NTSTATUS_IPSEC_QUEUE_OVERFLOW => Some("STATUS_IPSEC_QUEUE_OVERFLOW"), + SMB_NTSTATUS_ND_QUEUE_OVERFLOW => Some("STATUS_ND_QUEUE_OVERFLOW"), + SMB_NTSTATUS_HOPLIMIT_EXCEEDED => Some("STATUS_HOPLIMIT_EXCEEDED"), + SMB_NTSTATUS_PROTOCOL_NOT_SUPPORTED => Some("STATUS_PROTOCOL_NOT_SUPPORTED"), + SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED => Some("STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED"), + SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR => Some("STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR"), + SMB_NTSTATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR => Some("STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR"), + SMB_NTSTATUS_XML_PARSE_ERROR => Some("STATUS_XML_PARSE_ERROR"), + SMB_NTSTATUS_XMLDSIG_ERROR => Some("STATUS_XMLDSIG_ERROR"), + SMB_NTSTATUS_WRONG_COMPARTMENT => Some("STATUS_WRONG_COMPARTMENT"), + SMB_NTSTATUS_AUTHIP_FAILURE => Some("STATUS_AUTHIP_FAILURE"), + SMB_NTSTATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS => Some("STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS"), + SMB_NTSTATUS_DS_OID_NOT_FOUND => Some("STATUS_DS_OID_NOT_FOUND"), + SMB_NTSTATUS_HASH_NOT_SUPPORTED => Some("STATUS_HASH_NOT_SUPPORTED"), + SMB_NTSTATUS_HASH_NOT_PRESENT => Some("STATUS_HASH_NOT_PRESENT"), + SMB_NTSTATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED => Some("STATUS_OFFLOAD_READ_FLT_NOT_SUPPORTED"), + SMB_NTSTATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED => Some("STATUS_OFFLOAD_WRITE_FLT_NOT_SUPPORTED"), + SMB_NTSTATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED => Some("STATUS_OFFLOAD_READ_FILE_NOT_SUPPORTED"), + SMB_NTSTATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED => Some("STATUS_OFFLOAD_WRITE_FILE_NOT_SUPPORTED"), + SMB_NTDBG_NO_STATE_CHANGE => Some("DBG_NO_STATE_CHANGE"), + SMB_NTDBG_APP_NOT_IDLE => Some("DBG_APP_NOT_IDLE"), + SMB_NTRPC_NT_INVALID_STRING_BINDING => Some("RPC_NT_INVALID_STRING_BINDING"), + SMB_NTRPC_NT_WRONG_KIND_OF_BINDING => Some("RPC_NT_WRONG_KIND_OF_BINDING"), + SMB_NTRPC_NT_INVALID_BINDING => Some("RPC_NT_INVALID_BINDING"), + SMB_NTRPC_NT_PROTSEQ_NOT_SUPPORTED => Some("RPC_NT_PROTSEQ_NOT_SUPPORTED"), + SMB_NTRPC_NT_INVALID_RPC_PROTSEQ => Some("RPC_NT_INVALID_RPC_PROTSEQ"), + SMB_NTRPC_NT_INVALID_STRING_UUID => Some("RPC_NT_INVALID_STRING_UUID"), + SMB_NTRPC_NT_INVALID_ENDPOINT_FORMAT => Some("RPC_NT_INVALID_ENDPOINT_FORMAT"), + SMB_NTRPC_NT_INVALID_NET_ADDR => Some("RPC_NT_INVALID_NET_ADDR"), + SMB_NTRPC_NT_NO_ENDPOINT_FOUND => Some("RPC_NT_NO_ENDPOINT_FOUND"), + SMB_NTRPC_NT_INVALID_TIMEOUT => Some("RPC_NT_INVALID_TIMEOUT"), + SMB_NTRPC_NT_OBJECT_NOT_FOUND => Some("RPC_NT_OBJECT_NOT_FOUND"), + SMB_NTRPC_NT_ALREADY_REGISTERED => Some("RPC_NT_ALREADY_REGISTERED"), + SMB_NTRPC_NT_TYPE_ALREADY_REGISTERED => Some("RPC_NT_TYPE_ALREADY_REGISTERED"), + SMB_NTRPC_NT_ALREADY_LISTENING => Some("RPC_NT_ALREADY_LISTENING"), + SMB_NTRPC_NT_NO_PROTSEQS_REGISTERED => Some("RPC_NT_NO_PROTSEQS_REGISTERED"), + SMB_NTRPC_NT_NOT_LISTENING => Some("RPC_NT_NOT_LISTENING"), + SMB_NTRPC_NT_UNKNOWN_MGR_TYPE => Some("RPC_NT_UNKNOWN_MGR_TYPE"), + SMB_NTRPC_NT_UNKNOWN_IF => Some("RPC_NT_UNKNOWN_IF"), + SMB_NTRPC_NT_NO_BINDINGS => Some("RPC_NT_NO_BINDINGS"), + SMB_NTRPC_NT_NO_PROTSEQS => Some("RPC_NT_NO_PROTSEQS"), + SMB_NTRPC_NT_CANT_CREATE_ENDPOINT => Some("RPC_NT_CANT_CREATE_ENDPOINT"), + SMB_NTRPC_NT_OUT_OF_RESOURCES => Some("RPC_NT_OUT_OF_RESOURCES"), + SMB_NTRPC_NT_SERVER_UNAVAILABLE => Some("RPC_NT_SERVER_UNAVAILABLE"), + SMB_NTRPC_NT_SERVER_TOO_BUSY => Some("RPC_NT_SERVER_TOO_BUSY"), + SMB_NTRPC_NT_INVALID_NETWORK_OPTIONS => Some("RPC_NT_INVALID_NETWORK_OPTIONS"), + SMB_NTRPC_NT_NO_CALL_ACTIVE => Some("RPC_NT_NO_CALL_ACTIVE"), + SMB_NTRPC_NT_CALL_FAILED => Some("RPC_NT_CALL_FAILED"), + SMB_NTRPC_NT_CALL_FAILED_DNE => Some("RPC_NT_CALL_FAILED_DNE"), + SMB_NTRPC_NT_PROTOCOL_ERROR => Some("RPC_NT_PROTOCOL_ERROR"), + SMB_NTRPC_NT_UNSUPPORTED_TRANS_SYN => Some("RPC_NT_UNSUPPORTED_TRANS_SYN"), + SMB_NTRPC_NT_UNSUPPORTED_TYPE => Some("RPC_NT_UNSUPPORTED_TYPE"), + SMB_NTRPC_NT_INVALID_TAG => Some("RPC_NT_INVALID_TAG"), + SMB_NTRPC_NT_INVALID_BOUND => Some("RPC_NT_INVALID_BOUND"), + SMB_NTRPC_NT_NO_ENTRY_NAME => Some("RPC_NT_NO_ENTRY_NAME"), + SMB_NTRPC_NT_INVALID_NAME_SYNTAX => Some("RPC_NT_INVALID_NAME_SYNTAX"), + SMB_NTRPC_NT_UNSUPPORTED_NAME_SYNTAX => Some("RPC_NT_UNSUPPORTED_NAME_SYNTAX"), + SMB_NTRPC_NT_UUID_NO_ADDRESS => Some("RPC_NT_UUID_NO_ADDRESS"), + SMB_NTRPC_NT_DUPLICATE_ENDPOINT => Some("RPC_NT_DUPLICATE_ENDPOINT"), + SMB_NTRPC_NT_UNKNOWN_AUTHN_TYPE => Some("RPC_NT_UNKNOWN_AUTHN_TYPE"), + SMB_NTRPC_NT_MAX_CALLS_TOO_SMALL => Some("RPC_NT_MAX_CALLS_TOO_SMALL"), + SMB_NTRPC_NT_STRING_TOO_LONG => Some("RPC_NT_STRING_TOO_LONG"), + SMB_NTRPC_NT_PROTSEQ_NOT_FOUND => Some("RPC_NT_PROTSEQ_NOT_FOUND"), + SMB_NTRPC_NT_PROCNUM_OUT_OF_RANGE => Some("RPC_NT_PROCNUM_OUT_OF_RANGE"), + SMB_NTRPC_NT_BINDING_HAS_NO_AUTH => Some("RPC_NT_BINDING_HAS_NO_AUTH"), + SMB_NTRPC_NT_UNKNOWN_AUTHN_SERVICE => Some("RPC_NT_UNKNOWN_AUTHN_SERVICE"), + SMB_NTRPC_NT_UNKNOWN_AUTHN_LEVEL => Some("RPC_NT_UNKNOWN_AUTHN_LEVEL"), + SMB_NTRPC_NT_INVALID_AUTH_IDENTITY => Some("RPC_NT_INVALID_AUTH_IDENTITY"), + SMB_NTRPC_NT_UNKNOWN_AUTHZ_SERVICE => Some("RPC_NT_UNKNOWN_AUTHZ_SERVICE"), + SMB_NTEPT_NT_INVALID_ENTRY => Some("EPT_NT_INVALID_ENTRY"), + SMB_NTEPT_NT_CANT_PERFORM_OP => Some("EPT_NT_CANT_PERFORM_OP"), + SMB_NTEPT_NT_NOT_REGISTERED => Some("EPT_NT_NOT_REGISTERED"), + SMB_NTRPC_NT_NOTHING_TO_EXPORT => Some("RPC_NT_NOTHING_TO_EXPORT"), + SMB_NTRPC_NT_INCOMPLETE_NAME => Some("RPC_NT_INCOMPLETE_NAME"), + SMB_NTRPC_NT_INVALID_VERS_OPTION => Some("RPC_NT_INVALID_VERS_OPTION"), + SMB_NTRPC_NT_NO_MORE_MEMBERS => Some("RPC_NT_NO_MORE_MEMBERS"), + SMB_NTRPC_NT_NOT_ALL_OBJS_UNEXPORTED => Some("RPC_NT_NOT_ALL_OBJS_UNEXPORTED"), + SMB_NTRPC_NT_INTERFACE_NOT_FOUND => Some("RPC_NT_INTERFACE_NOT_FOUND"), + SMB_NTRPC_NT_ENTRY_ALREADY_EXISTS => Some("RPC_NT_ENTRY_ALREADY_EXISTS"), + SMB_NTRPC_NT_ENTRY_NOT_FOUND => Some("RPC_NT_ENTRY_NOT_FOUND"), + SMB_NTRPC_NT_NAME_SERVICE_UNAVAILABLE => Some("RPC_NT_NAME_SERVICE_UNAVAILABLE"), + SMB_NTRPC_NT_INVALID_NAF_ID => Some("RPC_NT_INVALID_NAF_ID"), + SMB_NTRPC_NT_CANNOT_SUPPORT => Some("RPC_NT_CANNOT_SUPPORT"), + SMB_NTRPC_NT_NO_CONTEXT_AVAILABLE => Some("RPC_NT_NO_CONTEXT_AVAILABLE"), + SMB_NTRPC_NT_INTERNAL_ERROR => Some("RPC_NT_INTERNAL_ERROR"), + SMB_NTRPC_NT_ZERO_DIVIDE => Some("RPC_NT_ZERO_DIVIDE"), + SMB_NTRPC_NT_ADDRESS_ERROR => Some("RPC_NT_ADDRESS_ERROR"), + SMB_NTRPC_NT_FP_DIV_ZERO => Some("RPC_NT_FP_DIV_ZERO"), + SMB_NTRPC_NT_FP_UNDERFLOW => Some("RPC_NT_FP_UNDERFLOW"), + SMB_NTRPC_NT_FP_OVERFLOW => Some("RPC_NT_FP_OVERFLOW"), + SMB_NTRPC_NT_CALL_IN_PROGRESS => Some("RPC_NT_CALL_IN_PROGRESS"), + SMB_NTRPC_NT_NO_MORE_BINDINGS => Some("RPC_NT_NO_MORE_BINDINGS"), + SMB_NTRPC_NT_GROUP_MEMBER_NOT_FOUND => Some("RPC_NT_GROUP_MEMBER_NOT_FOUND"), + SMB_NTEPT_NT_CANT_CREATE => Some("EPT_NT_CANT_CREATE"), + SMB_NTRPC_NT_INVALID_OBJECT => Some("RPC_NT_INVALID_OBJECT"), + SMB_NTRPC_NT_NO_INTERFACES => Some("RPC_NT_NO_INTERFACES"), + SMB_NTRPC_NT_CALL_CANCELLED => Some("RPC_NT_CALL_CANCELLED"), + SMB_NTRPC_NT_BINDING_INCOMPLETE => Some("RPC_NT_BINDING_INCOMPLETE"), + SMB_NTRPC_NT_COMM_FAILURE => Some("RPC_NT_COMM_FAILURE"), + SMB_NTRPC_NT_UNSUPPORTED_AUTHN_LEVEL => Some("RPC_NT_UNSUPPORTED_AUTHN_LEVEL"), + SMB_NTRPC_NT_NO_PRINC_NAME => Some("RPC_NT_NO_PRINC_NAME"), + SMB_NTRPC_NT_NOT_RPC_ERROR => Some("RPC_NT_NOT_RPC_ERROR"), + SMB_NTRPC_NT_SEC_PKG_ERROR => Some("RPC_NT_SEC_PKG_ERROR"), + SMB_NTRPC_NT_NOT_CANCELLED => Some("RPC_NT_NOT_CANCELLED"), + SMB_NTRPC_NT_INVALID_ASYNC_HANDLE => Some("RPC_NT_INVALID_ASYNC_HANDLE"), + SMB_NTRPC_NT_INVALID_ASYNC_CALL => Some("RPC_NT_INVALID_ASYNC_CALL"), + SMB_NTRPC_NT_PROXY_ACCESS_DENIED => Some("RPC_NT_PROXY_ACCESS_DENIED"), + SMB_NTRPC_NT_NO_MORE_ENTRIES => Some("RPC_NT_NO_MORE_ENTRIES"), + SMB_NTRPC_NT_SS_CHAR_TRANS_OPEN_FAIL => Some("RPC_NT_SS_CHAR_TRANS_OPEN_FAIL"), + SMB_NTRPC_NT_SS_CHAR_TRANS_SHORT_FILE => Some("RPC_NT_SS_CHAR_TRANS_SHORT_FILE"), + SMB_NTRPC_NT_SS_IN_NULL_CONTEXT => Some("RPC_NT_SS_IN_NULL_CONTEXT"), + SMB_NTRPC_NT_SS_CONTEXT_MISMATCH => Some("RPC_NT_SS_CONTEXT_MISMATCH"), + SMB_NTRPC_NT_SS_CONTEXT_DAMAGED => Some("RPC_NT_SS_CONTEXT_DAMAGED"), + SMB_NTRPC_NT_SS_HANDLES_MISMATCH => Some("RPC_NT_SS_HANDLES_MISMATCH"), + SMB_NTRPC_NT_SS_CANNOT_GET_CALL_HANDLE => Some("RPC_NT_SS_CANNOT_GET_CALL_HANDLE"), + SMB_NTRPC_NT_NULL_REF_POINTER => Some("RPC_NT_NULL_REF_POINTER"), + SMB_NTRPC_NT_ENUM_VALUE_OUT_OF_RANGE => Some("RPC_NT_ENUM_VALUE_OUT_OF_RANGE"), + SMB_NTRPC_NT_BYTE_COUNT_TOO_SMALL => Some("RPC_NT_BYTE_COUNT_TOO_SMALL"), + SMB_NTRPC_NT_BAD_STUB_DATA => Some("RPC_NT_BAD_STUB_DATA"), + SMB_NTRPC_NT_INVALID_ES_ACTION => Some("RPC_NT_INVALID_ES_ACTION"), + SMB_NTRPC_NT_WRONG_ES_VERSION => Some("RPC_NT_WRONG_ES_VERSION"), + SMB_NTRPC_NT_WRONG_STUB_VERSION => Some("RPC_NT_WRONG_STUB_VERSION"), + SMB_NTRPC_NT_INVALID_PIPE_OBJECT => Some("RPC_NT_INVALID_PIPE_OBJECT"), + SMB_NTRPC_NT_INVALID_PIPE_OPERATION => Some("RPC_NT_INVALID_PIPE_OPERATION"), + SMB_NTRPC_NT_WRONG_PIPE_VERSION => Some("RPC_NT_WRONG_PIPE_VERSION"), + SMB_NTRPC_NT_PIPE_CLOSED => Some("RPC_NT_PIPE_CLOSED"), + SMB_NTRPC_NT_PIPE_DISCIPLINE_ERROR => Some("RPC_NT_PIPE_DISCIPLINE_ERROR"), + SMB_NTRPC_NT_PIPE_EMPTY => Some("RPC_NT_PIPE_EMPTY"), + SMB_NTSTATUS_PNP_BAD_MPS_TABLE => Some("STATUS_PNP_BAD_MPS_TABLE"), + SMB_NTSTATUS_PNP_TRANSLATION_FAILED => Some("STATUS_PNP_TRANSLATION_FAILED"), + SMB_NTSTATUS_PNP_IRQ_TRANSLATION_FAILED => Some("STATUS_PNP_IRQ_TRANSLATION_FAILED"), + SMB_NTSTATUS_PNP_INVALID_ID => Some("STATUS_PNP_INVALID_ID"), + SMB_NTSTATUS_IO_REISSUE_AS_CACHED => Some("STATUS_IO_REISSUE_AS_CACHED"), + SMB_NTSTATUS_CTX_WINSTATION_NAME_INVALID => Some("STATUS_CTX_WINSTATION_NAME_INVALID"), + SMB_NTSTATUS_CTX_INVALID_PD => Some("STATUS_CTX_INVALID_PD"), + SMB_NTSTATUS_CTX_PD_NOT_FOUND => Some("STATUS_CTX_PD_NOT_FOUND"), + SMB_NTSTATUS_CTX_CLOSE_PENDING => Some("STATUS_CTX_CLOSE_PENDING"), + SMB_NTSTATUS_CTX_NO_OUTBUF => Some("STATUS_CTX_NO_OUTBUF"), + SMB_NTSTATUS_CTX_MODEM_INF_NOT_FOUND => Some("STATUS_CTX_MODEM_INF_NOT_FOUND"), + SMB_NTSTATUS_CTX_INVALID_MODEMNAME => Some("STATUS_CTX_INVALID_MODEMNAME"), + SMB_NTSTATUS_CTX_RESPONSE_ERROR => Some("STATUS_CTX_RESPONSE_ERROR"), + SMB_NTSTATUS_CTX_MODEM_RESPONSE_TIMEOUT => Some("STATUS_CTX_MODEM_RESPONSE_TIMEOUT"), + SMB_NTSTATUS_CTX_MODEM_RESPONSE_NO_CARRIER => Some("STATUS_CTX_MODEM_RESPONSE_NO_CARRIER"), + SMB_NTSTATUS_CTX_MODEM_RESPONSE_NO_DIALTONE => Some("STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE"), + SMB_NTSTATUS_CTX_MODEM_RESPONSE_BUSY => Some("STATUS_CTX_MODEM_RESPONSE_BUSY"), + SMB_NTSTATUS_CTX_MODEM_RESPONSE_VOICE => Some("STATUS_CTX_MODEM_RESPONSE_VOICE"), + SMB_NTSTATUS_CTX_TD_ERROR => Some("STATUS_CTX_TD_ERROR"), + SMB_NTSTATUS_CTX_LICENSE_CLIENT_INVALID => Some("STATUS_CTX_LICENSE_CLIENT_INVALID"), + SMB_NTSTATUS_CTX_LICENSE_NOT_AVAILABLE => Some("STATUS_CTX_LICENSE_NOT_AVAILABLE"), + SMB_NTSTATUS_CTX_LICENSE_EXPIRED => Some("STATUS_CTX_LICENSE_EXPIRED"), + SMB_NTSTATUS_CTX_WINSTATION_NOT_FOUND => Some("STATUS_CTX_WINSTATION_NOT_FOUND"), + SMB_NTSTATUS_CTX_WINSTATION_NAME_COLLISION => Some("STATUS_CTX_WINSTATION_NAME_COLLISION"), + SMB_NTSTATUS_CTX_WINSTATION_BUSY => Some("STATUS_CTX_WINSTATION_BUSY"), + SMB_NTSTATUS_CTX_BAD_VIDEO_MODE => Some("STATUS_CTX_BAD_VIDEO_MODE"), + SMB_NTSTATUS_CTX_GRAPHICS_INVALID => Some("STATUS_CTX_GRAPHICS_INVALID"), + SMB_NTSTATUS_CTX_NOT_CONSOLE => Some("STATUS_CTX_NOT_CONSOLE"), + SMB_NTSTATUS_CTX_CLIENT_QUERY_TIMEOUT => Some("STATUS_CTX_CLIENT_QUERY_TIMEOUT"), + SMB_NTSTATUS_CTX_CONSOLE_DISCONNECT => Some("STATUS_CTX_CONSOLE_DISCONNECT"), + SMB_NTSTATUS_CTX_CONSOLE_CONNECT => Some("STATUS_CTX_CONSOLE_CONNECT"), + SMB_NTSTATUS_CTX_SHADOW_DENIED => Some("STATUS_CTX_SHADOW_DENIED"), + SMB_NTSTATUS_CTX_WINSTATION_ACCESS_DENIED => Some("STATUS_CTX_WINSTATION_ACCESS_DENIED"), + SMB_NTSTATUS_CTX_INVALID_WD => Some("STATUS_CTX_INVALID_WD"), + SMB_NTSTATUS_CTX_WD_NOT_FOUND => Some("STATUS_CTX_WD_NOT_FOUND"), + SMB_NTSTATUS_CTX_SHADOW_INVALID => Some("STATUS_CTX_SHADOW_INVALID"), + SMB_NTSTATUS_CTX_SHADOW_DISABLED => Some("STATUS_CTX_SHADOW_DISABLED"), + SMB_NTSTATUS_RDP_PROTOCOL_ERROR => Some("STATUS_RDP_PROTOCOL_ERROR"), + SMB_NTSTATUS_CTX_CLIENT_LICENSE_NOT_SET => Some("STATUS_CTX_CLIENT_LICENSE_NOT_SET"), + SMB_NTSTATUS_CTX_CLIENT_LICENSE_IN_USE => Some("STATUS_CTX_CLIENT_LICENSE_IN_USE"), + SMB_NTSTATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE => Some("STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE"), + SMB_NTSTATUS_CTX_SHADOW_NOT_RUNNING => Some("STATUS_CTX_SHADOW_NOT_RUNNING"), + SMB_NTSTATUS_CTX_LOGON_DISABLED => Some("STATUS_CTX_LOGON_DISABLED"), + SMB_NTSTATUS_CTX_SECURITY_LAYER_ERROR => Some("STATUS_CTX_SECURITY_LAYER_ERROR"), + SMB_NTSTATUS_TS_INCOMPATIBLE_SESSIONS => Some("STATUS_TS_INCOMPATIBLE_SESSIONS"), + SMB_NTSTATUS_MUI_FILE_NOT_FOUND => Some("STATUS_MUI_FILE_NOT_FOUND"), + SMB_NTSTATUS_MUI_INVALID_FILE => Some("STATUS_MUI_INVALID_FILE"), + SMB_NTSTATUS_MUI_INVALID_RC_CONFIG => Some("STATUS_MUI_INVALID_RC_CONFIG"), + SMB_NTSTATUS_MUI_INVALID_LOCALE_NAME => Some("STATUS_MUI_INVALID_LOCALE_NAME"), + SMB_NTSTATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME => Some("STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME"), + SMB_NTSTATUS_MUI_FILE_NOT_LOADED => Some("STATUS_MUI_FILE_NOT_LOADED"), + SMB_NTSTATUS_RESOURCE_ENUM_USER_STOP => Some("STATUS_RESOURCE_ENUM_USER_STOP"), + SMB_NTSTATUS_CLUSTER_INVALID_NODE => Some("STATUS_CLUSTER_INVALID_NODE"), + SMB_NTSTATUS_CLUSTER_NODE_EXISTS => Some("STATUS_CLUSTER_NODE_EXISTS"), + SMB_NTSTATUS_CLUSTER_JOIN_IN_PROGRESS => Some("STATUS_CLUSTER_JOIN_IN_PROGRESS"), + SMB_NTSTATUS_CLUSTER_NODE_NOT_FOUND => Some("STATUS_CLUSTER_NODE_NOT_FOUND"), + SMB_NTSTATUS_CLUSTER_LOCAL_NODE_NOT_FOUND => Some("STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND"), + SMB_NTSTATUS_CLUSTER_NETWORK_EXISTS => Some("STATUS_CLUSTER_NETWORK_EXISTS"), + SMB_NTSTATUS_CLUSTER_NETWORK_NOT_FOUND => Some("STATUS_CLUSTER_NETWORK_NOT_FOUND"), + SMB_NTSTATUS_CLUSTER_NETINTERFACE_EXISTS => Some("STATUS_CLUSTER_NETINTERFACE_EXISTS"), + SMB_NTSTATUS_CLUSTER_NETINTERFACE_NOT_FOUND => Some("STATUS_CLUSTER_NETINTERFACE_NOT_FOUND"), + SMB_NTSTATUS_CLUSTER_INVALID_REQUEST => Some("STATUS_CLUSTER_INVALID_REQUEST"), + SMB_NTSTATUS_CLUSTER_INVALID_NETWORK_PROVIDER => Some("STATUS_CLUSTER_INVALID_NETWORK_PROVIDER"), + SMB_NTSTATUS_CLUSTER_NODE_DOWN => Some("STATUS_CLUSTER_NODE_DOWN"), + SMB_NTSTATUS_CLUSTER_NODE_UNREACHABLE => Some("STATUS_CLUSTER_NODE_UNREACHABLE"), + SMB_NTSTATUS_CLUSTER_NODE_NOT_MEMBER => Some("STATUS_CLUSTER_NODE_NOT_MEMBER"), + SMB_NTSTATUS_CLUSTER_JOIN_NOT_IN_PROGRESS => Some("STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS"), + SMB_NTSTATUS_CLUSTER_INVALID_NETWORK => Some("STATUS_CLUSTER_INVALID_NETWORK"), + SMB_NTSTATUS_CLUSTER_NO_NET_ADAPTERS => Some("STATUS_CLUSTER_NO_NET_ADAPTERS"), + SMB_NTSTATUS_CLUSTER_NODE_UP => Some("STATUS_CLUSTER_NODE_UP"), + SMB_NTSTATUS_CLUSTER_NODE_PAUSED => Some("STATUS_CLUSTER_NODE_PAUSED"), + SMB_NTSTATUS_CLUSTER_NODE_NOT_PAUSED => Some("STATUS_CLUSTER_NODE_NOT_PAUSED"), + SMB_NTSTATUS_CLUSTER_NO_SECURITY_CONTEXT => Some("STATUS_CLUSTER_NO_SECURITY_CONTEXT"), + SMB_NTSTATUS_CLUSTER_NETWORK_NOT_INTERNAL => Some("STATUS_CLUSTER_NETWORK_NOT_INTERNAL"), + SMB_NTSTATUS_CLUSTER_POISONED => Some("STATUS_CLUSTER_POISONED"), + SMB_NTSTATUS_ACPI_INVALID_OPCODE => Some("STATUS_ACPI_INVALID_OPCODE"), + SMB_NTSTATUS_ACPI_STACK_OVERFLOW => Some("STATUS_ACPI_STACK_OVERFLOW"), + SMB_NTSTATUS_ACPI_ASSERT_FAILED => Some("STATUS_ACPI_ASSERT_FAILED"), + SMB_NTSTATUS_ACPI_INVALID_INDEX => Some("STATUS_ACPI_INVALID_INDEX"), + SMB_NTSTATUS_ACPI_INVALID_ARGUMENT => Some("STATUS_ACPI_INVALID_ARGUMENT"), + SMB_NTSTATUS_ACPI_FATAL => Some("STATUS_ACPI_FATAL"), + SMB_NTSTATUS_ACPI_INVALID_SUPERNAME => Some("STATUS_ACPI_INVALID_SUPERNAME"), + SMB_NTSTATUS_ACPI_INVALID_ARGTYPE => Some("STATUS_ACPI_INVALID_ARGTYPE"), + SMB_NTSTATUS_ACPI_INVALID_OBJTYPE => Some("STATUS_ACPI_INVALID_OBJTYPE"), + SMB_NTSTATUS_ACPI_INVALID_TARGETTYPE => Some("STATUS_ACPI_INVALID_TARGETTYPE"), + SMB_NTSTATUS_ACPI_INCORRECT_ARGUMENT_COUNT => Some("STATUS_ACPI_INCORRECT_ARGUMENT_COUNT"), + SMB_NTSTATUS_ACPI_ADDRESS_NOT_MAPPED => Some("STATUS_ACPI_ADDRESS_NOT_MAPPED"), + SMB_NTSTATUS_ACPI_INVALID_EVENTTYPE => Some("STATUS_ACPI_INVALID_EVENTTYPE"), + SMB_NTSTATUS_ACPI_HANDLER_COLLISION => Some("STATUS_ACPI_HANDLER_COLLISION"), + SMB_NTSTATUS_ACPI_INVALID_DATA => Some("STATUS_ACPI_INVALID_DATA"), + SMB_NTSTATUS_ACPI_INVALID_REGION => Some("STATUS_ACPI_INVALID_REGION"), + SMB_NTSTATUS_ACPI_INVALID_ACCESS_SIZE => Some("STATUS_ACPI_INVALID_ACCESS_SIZE"), + SMB_NTSTATUS_ACPI_ACQUIRE_GLOBAL_LOCK => Some("STATUS_ACPI_ACQUIRE_GLOBAL_LOCK"), + SMB_NTSTATUS_ACPI_ALREADY_INITIALIZED => Some("STATUS_ACPI_ALREADY_INITIALIZED"), + SMB_NTSTATUS_ACPI_NOT_INITIALIZED => Some("STATUS_ACPI_NOT_INITIALIZED"), + SMB_NTSTATUS_ACPI_INVALID_MUTEX_LEVEL => Some("STATUS_ACPI_INVALID_MUTEX_LEVEL"), + SMB_NTSTATUS_ACPI_MUTEX_NOT_OWNED => Some("STATUS_ACPI_MUTEX_NOT_OWNED"), + SMB_NTSTATUS_ACPI_MUTEX_NOT_OWNER => Some("STATUS_ACPI_MUTEX_NOT_OWNER"), + SMB_NTSTATUS_ACPI_RS_ACCESS => Some("STATUS_ACPI_RS_ACCESS"), + SMB_NTSTATUS_ACPI_INVALID_TABLE => Some("STATUS_ACPI_INVALID_TABLE"), + SMB_NTSTATUS_ACPI_REG_HANDLER_FAILED => Some("STATUS_ACPI_REG_HANDLER_FAILED"), + SMB_NTSTATUS_ACPI_POWER_REQUEST_FAILED => Some("STATUS_ACPI_POWER_REQUEST_FAILED"), + SMB_NTSTATUS_SXS_SECTION_NOT_FOUND => Some("STATUS_SXS_SECTION_NOT_FOUND"), + SMB_NTSTATUS_SXS_CANT_GEN_ACTCTX => Some("STATUS_SXS_CANT_GEN_ACTCTX"), + SMB_NTSTATUS_SXS_INVALID_ACTCTXDATA_FORMAT => Some("STATUS_SXS_INVALID_ACTCTXDATA_FORMAT"), + SMB_NTSTATUS_SXS_ASSEMBLY_NOT_FOUND => Some("STATUS_SXS_ASSEMBLY_NOT_FOUND"), + SMB_NTSTATUS_SXS_MANIFEST_FORMAT_ERROR => Some("STATUS_SXS_MANIFEST_FORMAT_ERROR"), + SMB_NTSTATUS_SXS_MANIFEST_PARSE_ERROR => Some("STATUS_SXS_MANIFEST_PARSE_ERROR"), + SMB_NTSTATUS_SXS_ACTIVATION_CONTEXT_DISABLED => Some("STATUS_SXS_ACTIVATION_CONTEXT_DISABLED"), + SMB_NTSTATUS_SXS_KEY_NOT_FOUND => Some("STATUS_SXS_KEY_NOT_FOUND"), + SMB_NTSTATUS_SXS_VERSION_CONFLICT => Some("STATUS_SXS_VERSION_CONFLICT"), + SMB_NTSTATUS_SXS_WRONG_SECTION_TYPE => Some("STATUS_SXS_WRONG_SECTION_TYPE"), + SMB_NTSTATUS_SXS_THREAD_QUERIES_DISABLED => Some("STATUS_SXS_THREAD_QUERIES_DISABLED"), + SMB_NTSTATUS_SXS_ASSEMBLY_MISSING => Some("STATUS_SXS_ASSEMBLY_MISSING"), + SMB_NTSTATUS_SXS_PROCESS_DEFAULT_ALREADY_SET => Some("STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET"), + SMB_NTSTATUS_SXS_EARLY_DEACTIVATION => Some("STATUS_SXS_EARLY_DEACTIVATION"), + SMB_NTSTATUS_SXS_INVALID_DEACTIVATION => Some("STATUS_SXS_INVALID_DEACTIVATION"), + SMB_NTSTATUS_SXS_MULTIPLE_DEACTIVATION => Some("STATUS_SXS_MULTIPLE_DEACTIVATION"), + SMB_NTSTATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY => Some("STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY"), + SMB_NTSTATUS_SXS_PROCESS_TERMINATION_REQUESTED => Some("STATUS_SXS_PROCESS_TERMINATION_REQUESTED"), + SMB_NTSTATUS_SXS_CORRUPT_ACTIVATION_STACK => Some("STATUS_SXS_CORRUPT_ACTIVATION_STACK"), + SMB_NTSTATUS_SXS_CORRUPTION => Some("STATUS_SXS_CORRUPTION"), + SMB_NTSTATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE => Some("STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE"), + SMB_NTSTATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME => Some("STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME"), + SMB_NTSTATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE => Some("STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE"), + SMB_NTSTATUS_SXS_IDENTITY_PARSE_ERROR => Some("STATUS_SXS_IDENTITY_PARSE_ERROR"), + SMB_NTSTATUS_SXS_COMPONENT_STORE_CORRUPT => Some("STATUS_SXS_COMPONENT_STORE_CORRUPT"), + SMB_NTSTATUS_SXS_FILE_HASH_MISMATCH => Some("STATUS_SXS_FILE_HASH_MISMATCH"), + SMB_NTSTATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT => Some("STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT"), + SMB_NTSTATUS_SXS_IDENTITIES_DIFFERENT => Some("STATUS_SXS_IDENTITIES_DIFFERENT"), + SMB_NTSTATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT => Some("STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT"), + SMB_NTSTATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY => Some("STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY"), + SMB_NTSTATUS_ADVANCED_INSTALLER_FAILED => Some("STATUS_ADVANCED_INSTALLER_FAILED"), + SMB_NTSTATUS_XML_ENCODING_MISMATCH => Some("STATUS_XML_ENCODING_MISMATCH"), + SMB_NTSTATUS_SXS_MANIFEST_TOO_BIG => Some("STATUS_SXS_MANIFEST_TOO_BIG"), + SMB_NTSTATUS_SXS_SETTING_NOT_REGISTERED => Some("STATUS_SXS_SETTING_NOT_REGISTERED"), + SMB_NTSTATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE => Some("STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE"), + SMB_NTSTATUS_SMI_PRIMITIVE_INSTALLER_FAILED => Some("STATUS_SMI_PRIMITIVE_INSTALLER_FAILED"), + SMB_NTSTATUS_GENERIC_COMMAND_FAILED => Some("STATUS_GENERIC_COMMAND_FAILED"), + SMB_NTSTATUS_SXS_FILE_HASH_MISSING => Some("STATUS_SXS_FILE_HASH_MISSING"), + SMB_NTSTATUS_TRANSACTIONAL_CONFLICT => Some("STATUS_TRANSACTIONAL_CONFLICT"), + SMB_NTSTATUS_INVALID_TRANSACTION => Some("STATUS_INVALID_TRANSACTION"), + SMB_NTSTATUS_TRANSACTION_NOT_ACTIVE => Some("STATUS_TRANSACTION_NOT_ACTIVE"), + SMB_NTSTATUS_TM_INITIALIZATION_FAILED => Some("STATUS_TM_INITIALIZATION_FAILED"), + SMB_NTSTATUS_RM_NOT_ACTIVE => Some("STATUS_RM_NOT_ACTIVE"), + SMB_NTSTATUS_RM_METADATA_CORRUPT => Some("STATUS_RM_METADATA_CORRUPT"), + SMB_NTSTATUS_TRANSACTION_NOT_JOINED => Some("STATUS_TRANSACTION_NOT_JOINED"), + SMB_NTSTATUS_DIRECTORY_NOT_RM => Some("STATUS_DIRECTORY_NOT_RM"), + SMB_NTSTATUS_TRANSACTIONS_UNSUPPORTED_REMOTE => Some("STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE"), + SMB_NTSTATUS_LOG_RESIZE_INVALID_SIZE => Some("STATUS_LOG_RESIZE_INVALID_SIZE"), + SMB_NTSTATUS_REMOTE_FILE_VERSION_MISMATCH => Some("STATUS_REMOTE_FILE_VERSION_MISMATCH"), + SMB_NTSTATUS_CRM_PROTOCOL_ALREADY_EXISTS => Some("STATUS_CRM_PROTOCOL_ALREADY_EXISTS"), + SMB_NTSTATUS_TRANSACTION_PROPAGATION_FAILED => Some("STATUS_TRANSACTION_PROPAGATION_FAILED"), + SMB_NTSTATUS_CRM_PROTOCOL_NOT_FOUND => Some("STATUS_CRM_PROTOCOL_NOT_FOUND"), + SMB_NTSTATUS_TRANSACTION_SUPERIOR_EXISTS => Some("STATUS_TRANSACTION_SUPERIOR_EXISTS"), + SMB_NTSTATUS_TRANSACTION_REQUEST_NOT_VALID => Some("STATUS_TRANSACTION_REQUEST_NOT_VALID"), + SMB_NTSTATUS_TRANSACTION_NOT_REQUESTED => Some("STATUS_TRANSACTION_NOT_REQUESTED"), + SMB_NTSTATUS_TRANSACTION_ALREADY_ABORTED => Some("STATUS_TRANSACTION_ALREADY_ABORTED"), + SMB_NTSTATUS_TRANSACTION_ALREADY_COMMITTED => Some("STATUS_TRANSACTION_ALREADY_COMMITTED"), + SMB_NTSTATUS_TRANSACTION_INVALID_MARSHALL_BUFFER => Some("STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER"), + SMB_NTSTATUS_CURRENT_TRANSACTION_NOT_VALID => Some("STATUS_CURRENT_TRANSACTION_NOT_VALID"), + SMB_NTSTATUS_LOG_GROWTH_FAILED => Some("STATUS_LOG_GROWTH_FAILED"), + SMB_NTSTATUS_OBJECT_NO_LONGER_EXISTS => Some("STATUS_OBJECT_NO_LONGER_EXISTS"), + SMB_NTSTATUS_STREAM_MINIVERSION_NOT_FOUND => Some("STATUS_STREAM_MINIVERSION_NOT_FOUND"), + SMB_NTSTATUS_STREAM_MINIVERSION_NOT_VALID => Some("STATUS_STREAM_MINIVERSION_NOT_VALID"), + SMB_NTSTATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION => Some("STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION"), + SMB_NTSTATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT => Some("STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT"), + SMB_NTSTATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS => Some("STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS"), + SMB_NTSTATUS_HANDLE_NO_LONGER_VALID => Some("STATUS_HANDLE_NO_LONGER_VALID"), + SMB_NTSTATUS_LOG_CORRUPTION_DETECTED => Some("STATUS_LOG_CORRUPTION_DETECTED"), + SMB_NTSTATUS_RM_DISCONNECTED => Some("STATUS_RM_DISCONNECTED"), + SMB_NTSTATUS_ENLISTMENT_NOT_SUPERIOR => Some("STATUS_ENLISTMENT_NOT_SUPERIOR"), + SMB_NTSTATUS_FILE_IDENTITY_NOT_PERSISTENT => Some("STATUS_FILE_IDENTITY_NOT_PERSISTENT"), + SMB_NTSTATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY => Some("STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY"), + SMB_NTSTATUS_CANT_CROSS_RM_BOUNDARY => Some("STATUS_CANT_CROSS_RM_BOUNDARY"), + SMB_NTSTATUS_TXF_DIR_NOT_EMPTY => Some("STATUS_TXF_DIR_NOT_EMPTY"), + SMB_NTSTATUS_INDOUBT_TRANSACTIONS_EXIST => Some("STATUS_INDOUBT_TRANSACTIONS_EXIST"), + SMB_NTSTATUS_TM_VOLATILE => Some("STATUS_TM_VOLATILE"), + SMB_NTSTATUS_ROLLBACK_TIMER_EXPIRED => Some("STATUS_ROLLBACK_TIMER_EXPIRED"), + SMB_NTSTATUS_TXF_ATTRIBUTE_CORRUPT => Some("STATUS_TXF_ATTRIBUTE_CORRUPT"), + SMB_NTSTATUS_EFS_NOT_ALLOWED_IN_TRANSACTION => Some("STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION"), + SMB_NTSTATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED => Some("STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED"), + SMB_NTSTATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE => Some("STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE"), + SMB_NTSTATUS_TRANSACTION_REQUIRED_PROMOTION => Some("STATUS_TRANSACTION_REQUIRED_PROMOTION"), + SMB_NTSTATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION => Some("STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION"), + SMB_NTSTATUS_TRANSACTIONS_NOT_FROZEN => Some("STATUS_TRANSACTIONS_NOT_FROZEN"), + SMB_NTSTATUS_TRANSACTION_FREEZE_IN_PROGRESS => Some("STATUS_TRANSACTION_FREEZE_IN_PROGRESS"), + SMB_NTSTATUS_NOT_SNAPSHOT_VOLUME => Some("STATUS_NOT_SNAPSHOT_VOLUME"), + SMB_NTSTATUS_NO_SAVEPOINT_WITH_OPEN_FILES => Some("STATUS_NO_SAVEPOINT_WITH_OPEN_FILES"), + SMB_NTSTATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION => Some("STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION"), + SMB_NTSTATUS_TM_IDENTITY_MISMATCH => Some("STATUS_TM_IDENTITY_MISMATCH"), + SMB_NTSTATUS_FLOATED_SECTION => Some("STATUS_FLOATED_SECTION"), + SMB_NTSTATUS_CANNOT_ACCEPT_TRANSACTED_WORK => Some("STATUS_CANNOT_ACCEPT_TRANSACTED_WORK"), + SMB_NTSTATUS_CANNOT_ABORT_TRANSACTIONS => Some("STATUS_CANNOT_ABORT_TRANSACTIONS"), + SMB_NTSTATUS_TRANSACTION_NOT_FOUND => Some("STATUS_TRANSACTION_NOT_FOUND"), + SMB_NTSTATUS_RESOURCEMANAGER_NOT_FOUND => Some("STATUS_RESOURCEMANAGER_NOT_FOUND"), + SMB_NTSTATUS_ENLISTMENT_NOT_FOUND => Some("STATUS_ENLISTMENT_NOT_FOUND"), + SMB_NTSTATUS_TRANSACTIONMANAGER_NOT_FOUND => Some("STATUS_TRANSACTIONMANAGER_NOT_FOUND"), + SMB_NTSTATUS_TRANSACTIONMANAGER_NOT_ONLINE => Some("STATUS_TRANSACTIONMANAGER_NOT_ONLINE"), + SMB_NTSTATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION => Some("STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION"), + SMB_NTSTATUS_TRANSACTION_NOT_ROOT => Some("STATUS_TRANSACTION_NOT_ROOT"), + SMB_NTSTATUS_TRANSACTION_OBJECT_EXPIRED => Some("STATUS_TRANSACTION_OBJECT_EXPIRED"), + SMB_NTSTATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION => Some("STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION"), + SMB_NTSTATUS_TRANSACTION_RESPONSE_NOT_ENLISTED => Some("STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED"), + SMB_NTSTATUS_TRANSACTION_RECORD_TOO_LONG => Some("STATUS_TRANSACTION_RECORD_TOO_LONG"), + SMB_NTSTATUS_NO_LINK_TRACKING_IN_TRANSACTION => Some("STATUS_NO_LINK_TRACKING_IN_TRANSACTION"), + SMB_NTSTATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION => Some("STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION"), + SMB_NTSTATUS_TRANSACTION_INTEGRITY_VIOLATED => Some("STATUS_TRANSACTION_INTEGRITY_VIOLATED"), + SMB_NTSTATUS_EXPIRED_HANDLE => Some("STATUS_EXPIRED_HANDLE"), + SMB_NTSTATUS_TRANSACTION_NOT_ENLISTED => Some("STATUS_TRANSACTION_NOT_ENLISTED"), + SMB_NTSTATUS_LOG_SECTOR_INVALID => Some("STATUS_LOG_SECTOR_INVALID"), + SMB_NTSTATUS_LOG_SECTOR_PARITY_INVALID => Some("STATUS_LOG_SECTOR_PARITY_INVALID"), + SMB_NTSTATUS_LOG_SECTOR_REMAPPED => Some("STATUS_LOG_SECTOR_REMAPPED"), + SMB_NTSTATUS_LOG_BLOCK_INCOMPLETE => Some("STATUS_LOG_BLOCK_INCOMPLETE"), + SMB_NTSTATUS_LOG_INVALID_RANGE => Some("STATUS_LOG_INVALID_RANGE"), + SMB_NTSTATUS_LOG_BLOCKS_EXHAUSTED => Some("STATUS_LOG_BLOCKS_EXHAUSTED"), + SMB_NTSTATUS_LOG_READ_CONTEXT_INVALID => Some("STATUS_LOG_READ_CONTEXT_INVALID"), + SMB_NTSTATUS_LOG_RESTART_INVALID => Some("STATUS_LOG_RESTART_INVALID"), + SMB_NTSTATUS_LOG_BLOCK_VERSION => Some("STATUS_LOG_BLOCK_VERSION"), + SMB_NTSTATUS_LOG_BLOCK_INVALID => Some("STATUS_LOG_BLOCK_INVALID"), + SMB_NTSTATUS_LOG_READ_MODE_INVALID => Some("STATUS_LOG_READ_MODE_INVALID"), + SMB_NTSTATUS_LOG_METADATA_CORRUPT => Some("STATUS_LOG_METADATA_CORRUPT"), + SMB_NTSTATUS_LOG_METADATA_INVALID => Some("STATUS_LOG_METADATA_INVALID"), + SMB_NTSTATUS_LOG_METADATA_INCONSISTENT => Some("STATUS_LOG_METADATA_INCONSISTENT"), + SMB_NTSTATUS_LOG_RESERVATION_INVALID => Some("STATUS_LOG_RESERVATION_INVALID"), + SMB_NTSTATUS_LOG_CANT_DELETE => Some("STATUS_LOG_CANT_DELETE"), + SMB_NTSTATUS_LOG_CONTAINER_LIMIT_EXCEEDED => Some("STATUS_LOG_CONTAINER_LIMIT_EXCEEDED"), + SMB_NTSTATUS_LOG_START_OF_LOG => Some("STATUS_LOG_START_OF_LOG"), + SMB_NTSTATUS_LOG_POLICY_ALREADY_INSTALLED => Some("STATUS_LOG_POLICY_ALREADY_INSTALLED"), + SMB_NTSTATUS_LOG_POLICY_NOT_INSTALLED => Some("STATUS_LOG_POLICY_NOT_INSTALLED"), + SMB_NTSTATUS_LOG_POLICY_INVALID => Some("STATUS_LOG_POLICY_INVALID"), + SMB_NTSTATUS_LOG_POLICY_CONFLICT => Some("STATUS_LOG_POLICY_CONFLICT"), + SMB_NTSTATUS_LOG_PINNED_ARCHIVE_TAIL => Some("STATUS_LOG_PINNED_ARCHIVE_TAIL"), + SMB_NTSTATUS_LOG_RECORD_NONEXISTENT => Some("STATUS_LOG_RECORD_NONEXISTENT"), + SMB_NTSTATUS_LOG_RECORDS_RESERVED_INVALID => Some("STATUS_LOG_RECORDS_RESERVED_INVALID"), + SMB_NTSTATUS_LOG_SPACE_RESERVED_INVALID => Some("STATUS_LOG_SPACE_RESERVED_INVALID"), + SMB_NTSTATUS_LOG_TAIL_INVALID => Some("STATUS_LOG_TAIL_INVALID"), + SMB_NTSTATUS_LOG_FULL => Some("STATUS_LOG_FULL"), + SMB_NTSTATUS_LOG_MULTIPLEXED => Some("STATUS_LOG_MULTIPLEXED"), + SMB_NTSTATUS_LOG_DEDICATED => Some("STATUS_LOG_DEDICATED"), + SMB_NTSTATUS_LOG_ARCHIVE_NOT_IN_PROGRESS => Some("STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS"), + SMB_NTSTATUS_LOG_ARCHIVE_IN_PROGRESS => Some("STATUS_LOG_ARCHIVE_IN_PROGRESS"), + SMB_NTSTATUS_LOG_EPHEMERAL => Some("STATUS_LOG_EPHEMERAL"), + SMB_NTSTATUS_LOG_NOT_ENOUGH_CONTAINERS => Some("STATUS_LOG_NOT_ENOUGH_CONTAINERS"), + SMB_NTSTATUS_LOG_CLIENT_ALREADY_REGISTERED => Some("STATUS_LOG_CLIENT_ALREADY_REGISTERED"), + SMB_NTSTATUS_LOG_CLIENT_NOT_REGISTERED => Some("STATUS_LOG_CLIENT_NOT_REGISTERED"), + SMB_NTSTATUS_LOG_FULL_HANDLER_IN_PROGRESS => Some("STATUS_LOG_FULL_HANDLER_IN_PROGRESS"), + SMB_NTSTATUS_LOG_CONTAINER_READ_FAILED => Some("STATUS_LOG_CONTAINER_READ_FAILED"), + SMB_NTSTATUS_LOG_CONTAINER_WRITE_FAILED => Some("STATUS_LOG_CONTAINER_WRITE_FAILED"), + SMB_NTSTATUS_LOG_CONTAINER_OPEN_FAILED => Some("STATUS_LOG_CONTAINER_OPEN_FAILED"), + SMB_NTSTATUS_LOG_CONTAINER_STATE_INVALID => Some("STATUS_LOG_CONTAINER_STATE_INVALID"), + SMB_NTSTATUS_LOG_STATE_INVALID => Some("STATUS_LOG_STATE_INVALID"), + SMB_NTSTATUS_LOG_PINNED => Some("STATUS_LOG_PINNED"), + SMB_NTSTATUS_LOG_METADATA_FLUSH_FAILED => Some("STATUS_LOG_METADATA_FLUSH_FAILED"), + SMB_NTSTATUS_LOG_INCONSISTENT_SECURITY => Some("STATUS_LOG_INCONSISTENT_SECURITY"), + SMB_NTSTATUS_LOG_APPENDED_FLUSH_FAILED => Some("STATUS_LOG_APPENDED_FLUSH_FAILED"), + SMB_NTSTATUS_LOG_PINNED_RESERVATION => Some("STATUS_LOG_PINNED_RESERVATION"), + SMB_NTSTATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD => Some("STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD"), + SMB_NTSTATUS_FLT_NO_HANDLER_DEFINED => Some("STATUS_FLT_NO_HANDLER_DEFINED"), + SMB_NTSTATUS_FLT_CONTEXT_ALREADY_DEFINED => Some("STATUS_FLT_CONTEXT_ALREADY_DEFINED"), + SMB_NTSTATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST => Some("STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST"), + SMB_NTSTATUS_FLT_DISALLOW_FAST_IO => Some("STATUS_FLT_DISALLOW_FAST_IO"), + SMB_NTSTATUS_FLT_INVALID_NAME_REQUEST => Some("STATUS_FLT_INVALID_NAME_REQUEST"), + SMB_NTSTATUS_FLT_NOT_SAFE_TO_POST_OPERATION => Some("STATUS_FLT_NOT_SAFE_TO_POST_OPERATION"), + SMB_NTSTATUS_FLT_NOT_INITIALIZED => Some("STATUS_FLT_NOT_INITIALIZED"), + SMB_NTSTATUS_FLT_FILTER_NOT_READY => Some("STATUS_FLT_FILTER_NOT_READY"), + SMB_NTSTATUS_FLT_POST_OPERATION_CLEANUP => Some("STATUS_FLT_POST_OPERATION_CLEANUP"), + SMB_NTSTATUS_FLT_INTERNAL_ERROR => Some("STATUS_FLT_INTERNAL_ERROR"), + SMB_NTSTATUS_FLT_DELETING_OBJECT => Some("STATUS_FLT_DELETING_OBJECT"), + SMB_NTSTATUS_FLT_MUST_BE_NONPAGED_POOL => Some("STATUS_FLT_MUST_BE_NONPAGED_POOL"), + SMB_NTSTATUS_FLT_DUPLICATE_ENTRY => Some("STATUS_FLT_DUPLICATE_ENTRY"), + SMB_NTSTATUS_FLT_CBDQ_DISABLED => Some("STATUS_FLT_CBDQ_DISABLED"), + SMB_NTSTATUS_FLT_DO_NOT_ATTACH => Some("STATUS_FLT_DO_NOT_ATTACH"), + SMB_NTSTATUS_FLT_DO_NOT_DETACH => Some("STATUS_FLT_DO_NOT_DETACH"), + SMB_NTSTATUS_FLT_INSTANCE_ALTITUDE_COLLISION => Some("STATUS_FLT_INSTANCE_ALTITUDE_COLLISION"), + SMB_NTSTATUS_FLT_INSTANCE_NAME_COLLISION => Some("STATUS_FLT_INSTANCE_NAME_COLLISION"), + SMB_NTSTATUS_FLT_FILTER_NOT_FOUND => Some("STATUS_FLT_FILTER_NOT_FOUND"), + SMB_NTSTATUS_FLT_VOLUME_NOT_FOUND => Some("STATUS_FLT_VOLUME_NOT_FOUND"), + SMB_NTSTATUS_FLT_INSTANCE_NOT_FOUND => Some("STATUS_FLT_INSTANCE_NOT_FOUND"), + SMB_NTSTATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND => Some("STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND"), + SMB_NTSTATUS_FLT_INVALID_CONTEXT_REGISTRATION => Some("STATUS_FLT_INVALID_CONTEXT_REGISTRATION"), + SMB_NTSTATUS_FLT_NAME_CACHE_MISS => Some("STATUS_FLT_NAME_CACHE_MISS"), + SMB_NTSTATUS_FLT_NO_DEVICE_OBJECT => Some("STATUS_FLT_NO_DEVICE_OBJECT"), + SMB_NTSTATUS_FLT_VOLUME_ALREADY_MOUNTED => Some("STATUS_FLT_VOLUME_ALREADY_MOUNTED"), + SMB_NTSTATUS_FLT_ALREADY_ENLISTED => Some("STATUS_FLT_ALREADY_ENLISTED"), + SMB_NTSTATUS_FLT_CONTEXT_ALREADY_LINKED => Some("STATUS_FLT_CONTEXT_ALREADY_LINKED"), + SMB_NTSTATUS_FLT_NO_WAITER_FOR_REPLY => Some("STATUS_FLT_NO_WAITER_FOR_REPLY"), + SMB_NTSTATUS_MONITOR_NO_DESCRIPTOR => Some("STATUS_MONITOR_NO_DESCRIPTOR"), + SMB_NTSTATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT => Some("STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT"), + SMB_NTSTATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM => Some("STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM"), + SMB_NTSTATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK => Some("STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK"), + SMB_NTSTATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED => Some("STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED"), + SMB_NTSTATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK => Some("STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK"), + SMB_NTSTATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK => Some("STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK"), + SMB_NTSTATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA => Some("STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA"), + SMB_NTSTATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK => Some("STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK"), + SMB_NTSTATUS_MONITOR_INVALID_MANUFACTURE_DATE => Some("STATUS_MONITOR_INVALID_MANUFACTURE_DATE"), + SMB_NTSTATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER => Some("STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER"), + SMB_NTSTATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER => Some("STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER"), + SMB_NTSTATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER => Some("STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER"), + SMB_NTSTATUS_GRAPHICS_ADAPTER_WAS_RESET => Some("STATUS_GRAPHICS_ADAPTER_WAS_RESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_DRIVER_MODEL => Some("STATUS_GRAPHICS_INVALID_DRIVER_MODEL"), + SMB_NTSTATUS_GRAPHICS_PRESENT_MODE_CHANGED => Some("STATUS_GRAPHICS_PRESENT_MODE_CHANGED"), + SMB_NTSTATUS_GRAPHICS_PRESENT_OCCLUDED => Some("STATUS_GRAPHICS_PRESENT_OCCLUDED"), + SMB_NTSTATUS_GRAPHICS_PRESENT_DENIED => Some("STATUS_GRAPHICS_PRESENT_DENIED"), + SMB_NTSTATUS_GRAPHICS_CANNOTCOLORCONVERT => Some("STATUS_GRAPHICS_CANNOTCOLORCONVERT"), + SMB_NTSTATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED => Some("STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED"), + SMB_NTSTATUS_GRAPHICS_PRESENT_UNOCCLUDED => Some("STATUS_GRAPHICS_PRESENT_UNOCCLUDED"), + SMB_NTSTATUS_GRAPHICS_NO_VIDEO_MEMORY => Some("STATUS_GRAPHICS_NO_VIDEO_MEMORY"), + SMB_NTSTATUS_GRAPHICS_CANT_LOCK_MEMORY => Some("STATUS_GRAPHICS_CANT_LOCK_MEMORY"), + SMB_NTSTATUS_GRAPHICS_ALLOCATION_BUSY => Some("STATUS_GRAPHICS_ALLOCATION_BUSY"), + SMB_NTSTATUS_GRAPHICS_TOO_MANY_REFERENCES => Some("STATUS_GRAPHICS_TOO_MANY_REFERENCES"), + SMB_NTSTATUS_GRAPHICS_TRY_AGAIN_LATER => Some("STATUS_GRAPHICS_TRY_AGAIN_LATER"), + SMB_NTSTATUS_GRAPHICS_TRY_AGAIN_NOW => Some("STATUS_GRAPHICS_TRY_AGAIN_NOW"), + SMB_NTSTATUS_GRAPHICS_ALLOCATION_INVALID => Some("STATUS_GRAPHICS_ALLOCATION_INVALID"), + SMB_NTSTATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE => Some("STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE"), + SMB_NTSTATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED => Some("STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED"), + SMB_NTSTATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION => Some("STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION"), + SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_USAGE => Some("STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE"), + SMB_NTSTATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION => Some("STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION"), + SMB_NTSTATUS_GRAPHICS_ALLOCATION_CLOSED => Some("STATUS_GRAPHICS_ALLOCATION_CLOSED"), + SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE => Some("STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE"), + SMB_NTSTATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE => Some("STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE"), + SMB_NTSTATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE => Some("STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE"), + SMB_NTSTATUS_GRAPHICS_ALLOCATION_CONTENT_LOST => Some("STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST"), + SMB_NTSTATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE => Some("STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY => Some("STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED => Some("STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED => Some("STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN => Some("STATUS_GRAPHICS_INVALID_VIDPN"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE => Some("STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET => Some("STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET"), + SMB_NTSTATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED => Some("STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET => Some("STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET => Some("STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_FREQUENCY => Some("STATUS_GRAPHICS_INVALID_FREQUENCY"), + SMB_NTSTATUS_GRAPHICS_INVALID_ACTIVE_REGION => Some("STATUS_GRAPHICS_INVALID_ACTIVE_REGION"), + SMB_NTSTATUS_GRAPHICS_INVALID_TOTAL_REGION => Some("STATUS_GRAPHICS_INVALID_TOTAL_REGION"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE => Some("STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE => Some("STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE"), + SMB_NTSTATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET => Some("STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET"), + SMB_NTSTATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY => Some("STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_MODE_ALREADY_IN_MODESET => Some("STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET => Some("STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET => Some("STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET"), + SMB_NTSTATUS_GRAPHICS_SOURCE_ALREADY_IN_SET => Some("STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET"), + SMB_NTSTATUS_GRAPHICS_TARGET_ALREADY_IN_SET => Some("STATUS_GRAPHICS_TARGET_ALREADY_IN_SET"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH => Some("STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH"), + SMB_NTSTATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY => Some("STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET => Some("STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE => Some("STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE"), + SMB_NTSTATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET => Some("STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET"), + SMB_NTSTATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET => Some("STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET"), + SMB_NTSTATUS_GRAPHICS_STALE_MODESET => Some("STATUS_GRAPHICS_STALE_MODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET => Some("STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE => Some("STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE"), + SMB_NTSTATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN => Some("STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN"), + SMB_NTSTATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE => Some("STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE"), + SMB_NTSTATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION => Some("STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION"), + SMB_NTSTATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES => Some("STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES"), + SMB_NTSTATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY => Some("STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE => Some("STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE"), + SMB_NTSTATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET => Some("STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET => Some("STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR => Some("STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR"), + SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET => Some("STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET"), + SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET => Some("STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET"), + SMB_NTSTATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE => Some("STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE => Some("STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE"), + SMB_NTSTATUS_GRAPHICS_RESOURCES_NOT_RELATED => Some("STATUS_GRAPHICS_RESOURCES_NOT_RELATED"), + SMB_NTSTATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE => Some("STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE"), + SMB_NTSTATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE => Some("STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE"), + SMB_NTSTATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET => Some("STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET"), + SMB_NTSTATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER => Some("STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER"), + SMB_NTSTATUS_GRAPHICS_NO_VIDPNMGR => Some("STATUS_GRAPHICS_NO_VIDPNMGR"), + SMB_NTSTATUS_GRAPHICS_NO_ACTIVE_VIDPN => Some("STATUS_GRAPHICS_NO_ACTIVE_VIDPN"), + SMB_NTSTATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY => Some("STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_MONITOR_NOT_CONNECTED => Some("STATUS_GRAPHICS_MONITOR_NOT_CONNECTED"), + SMB_NTSTATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY => Some("STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE => Some("STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE"), + SMB_NTSTATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE => Some("STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE"), + SMB_NTSTATUS_GRAPHICS_INVALID_STRIDE => Some("STATUS_GRAPHICS_INVALID_STRIDE"), + SMB_NTSTATUS_GRAPHICS_INVALID_PIXELFORMAT => Some("STATUS_GRAPHICS_INVALID_PIXELFORMAT"), + SMB_NTSTATUS_GRAPHICS_INVALID_COLORBASIS => Some("STATUS_GRAPHICS_INVALID_COLORBASIS"), + SMB_NTSTATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE => Some("STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE"), + SMB_NTSTATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY => Some("STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY"), + SMB_NTSTATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT => Some("STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT"), + SMB_NTSTATUS_GRAPHICS_VIDPN_SOURCE_IN_USE => Some("STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE"), + SMB_NTSTATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN => Some("STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN"), + SMB_NTSTATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL => Some("STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL"), + SMB_NTSTATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION => Some("STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION"), + SMB_NTSTATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED => Some("STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_INVALID_GAMMA_RAMP => Some("STATUS_GRAPHICS_INVALID_GAMMA_RAMP"), + SMB_NTSTATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED => Some("STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED => Some("STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_MODE_NOT_IN_MODESET => Some("STATUS_GRAPHICS_MODE_NOT_IN_MODESET"), + SMB_NTSTATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON => Some("STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON"), + SMB_NTSTATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE => Some("STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE"), + SMB_NTSTATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE => Some("STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE"), + SMB_NTSTATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS => Some("STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS"), + SMB_NTSTATUS_GRAPHICS_INVALID_SCANLINE_ORDERING => Some("STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING"), + SMB_NTSTATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED => Some("STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED"), + SMB_NTSTATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS => Some("STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS"), + SMB_NTSTATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT => Some("STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT"), + SMB_NTSTATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM => Some("STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN => Some("STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN"), + SMB_NTSTATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT => Some("STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT"), + SMB_NTSTATUS_GRAPHICS_MAX_NUM_PATHS_REACHED => Some("STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED"), + SMB_NTSTATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION => Some("STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION"), + SMB_NTSTATUS_GRAPHICS_INVALID_CLIENT_TYPE => Some("STATUS_GRAPHICS_INVALID_CLIENT_TYPE"), + SMB_NTSTATUS_GRAPHICS_CLIENTVIDPN_NOT_SET => Some("STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET"), + SMB_NTSTATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED => Some("STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED"), + SMB_NTSTATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED => Some("STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_NOT_A_LINKED_ADAPTER => Some("STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER"), + SMB_NTSTATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED => Some("STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED"), + SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED => Some("STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED"), + SMB_NTSTATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY => Some("STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY"), + SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_STARTED => Some("STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED"), + SMB_NTSTATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON => Some("STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON"), + SMB_NTSTATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE => Some("STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE"), + SMB_NTSTATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER => Some("STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER"), + SMB_NTSTATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED => Some("STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED"), + SMB_NTSTATUS_GRAPHICS_OPM_NOT_SUPPORTED => Some("STATUS_GRAPHICS_OPM_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_COPP_NOT_SUPPORTED => Some("STATUS_GRAPHICS_COPP_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_UAB_NOT_SUPPORTED => Some("STATUS_GRAPHICS_UAB_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS => Some("STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS"), + SMB_NTSTATUS_GRAPHICS_OPM_PARAMETER_ARRAY_TOO_SMALL => Some("STATUS_GRAPHICS_OPM_PARAMETER_ARRAY_TOO_SMALL"), + SMB_NTSTATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST => Some("STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST"), + SMB_NTSTATUS_GRAPHICS_PVP_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME => Some("STATUS_GRAPHICS_PVP_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME"), + SMB_NTSTATUS_GRAPHICS_PVP_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP => Some("STATUS_GRAPHICS_PVP_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP"), + SMB_NTSTATUS_GRAPHICS_PVP_MIRRORING_DEVICES_NOT_SUPPORTED => Some("STATUS_GRAPHICS_PVP_MIRRORING_DEVICES_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_POINTER => Some("STATUS_GRAPHICS_OPM_INVALID_POINTER"), + SMB_NTSTATUS_GRAPHICS_OPM_INTERNAL_ERROR => Some("STATUS_GRAPHICS_OPM_INTERNAL_ERROR"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_HANDLE => Some("STATUS_GRAPHICS_OPM_INVALID_HANDLE"), + SMB_NTSTATUS_GRAPHICS_PVP_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE => Some("STATUS_GRAPHICS_PVP_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE"), + SMB_NTSTATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH => Some("STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH"), + SMB_NTSTATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED => Some("STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED"), + SMB_NTSTATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED => Some("STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED"), + SMB_NTSTATUS_GRAPHICS_PVP_HFS_FAILED => Some("STATUS_GRAPHICS_PVP_HFS_FAILED"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_SRM => Some("STATUS_GRAPHICS_OPM_INVALID_SRM"), + SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP => Some("STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP"), + SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP => Some("STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP"), + SMB_NTSTATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA => Some("STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA"), + SMB_NTSTATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET => Some("STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET"), + SMB_NTSTATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH => Some("STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH"), + SMB_NTSTATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE => Some("STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE"), + SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS => Some("STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS"), + SMB_NTSTATUS_GRAPHICS_OPM_SESSION_TYPE_CHANGE_IN_PROGRESS => Some("STATUS_GRAPHICS_OPM_SESSION_TYPE_CHANGE_IN_PROGRESS"), + SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS => Some("STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST => Some("STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST"), + SMB_NTSTATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR => Some("STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR"), + SMB_NTSTATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS => Some("STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS"), + SMB_NTSTATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED => Some("STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST => Some("STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST"), + SMB_NTSTATUS_GRAPHICS_I2C_NOT_SUPPORTED => Some("STATUS_GRAPHICS_I2C_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST => Some("STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST"), + SMB_NTSTATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA => Some("STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA"), + SMB_NTSTATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA => Some("STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA"), + SMB_NTSTATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED => Some("STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_DATA => Some("STATUS_GRAPHICS_DDCCI_INVALID_DATA"), + SMB_NTSTATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE => Some("STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE"), + SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING => Some("STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING"), + SMB_NTSTATUS_GRAPHICS_MCA_INTERNAL_ERROR => Some("STATUS_GRAPHICS_MCA_INTERNAL_ERROR"), + SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND => Some("STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND"), + SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH => Some("STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH"), + SMB_NTSTATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM => Some("STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM"), + SMB_NTSTATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE => Some("STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE"), + SMB_NTSTATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS => Some("STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS"), + SMB_NTSTATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED => Some("STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME => Some("STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME"), + SMB_NTSTATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP => Some("STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP"), + SMB_NTSTATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED => Some("STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED"), + SMB_NTSTATUS_GRAPHICS_INVALID_POINTER => Some("STATUS_GRAPHICS_INVALID_POINTER"), + SMB_NTSTATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE => Some("STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE"), + SMB_NTSTATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL => Some("STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL"), + SMB_NTSTATUS_GRAPHICS_INTERNAL_ERROR => Some("STATUS_GRAPHICS_INTERNAL_ERROR"), + SMB_NTSTATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS => Some("STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS"), + SMB_NTSTATUS_FVE_LOCKED_VOLUME => Some("STATUS_FVE_LOCKED_VOLUME"), + SMB_NTSTATUS_FVE_NOT_ENCRYPTED => Some("STATUS_FVE_NOT_ENCRYPTED"), + SMB_NTSTATUS_FVE_BAD_INFORMATION => Some("STATUS_FVE_BAD_INFORMATION"), + SMB_NTSTATUS_FVE_TOO_SMALL => Some("STATUS_FVE_TOO_SMALL"), + SMB_NTSTATUS_FVE_FAILED_WRONG_FS => Some("STATUS_FVE_FAILED_WRONG_FS"), + SMB_NTSTATUS_FVE_FAILED_BAD_FS => Some("STATUS_FVE_FAILED_BAD_FS"), + SMB_NTSTATUS_FVE_FS_NOT_EXTENDED => Some("STATUS_FVE_FS_NOT_EXTENDED"), + SMB_NTSTATUS_FVE_FS_MOUNTED => Some("STATUS_FVE_FS_MOUNTED"), + SMB_NTSTATUS_FVE_NO_LICENSE => Some("STATUS_FVE_NO_LICENSE"), + SMB_NTSTATUS_FVE_ACTION_NOT_ALLOWED => Some("STATUS_FVE_ACTION_NOT_ALLOWED"), + SMB_NTSTATUS_FVE_BAD_DATA => Some("STATUS_FVE_BAD_DATA"), + SMB_NTSTATUS_FVE_VOLUME_NOT_BOUND => Some("STATUS_FVE_VOLUME_NOT_BOUND"), + SMB_NTSTATUS_FVE_NOT_DATA_VOLUME => Some("STATUS_FVE_NOT_DATA_VOLUME"), + SMB_NTSTATUS_FVE_CONV_READ_ERROR => Some("STATUS_FVE_CONV_READ_ERROR"), + SMB_NTSTATUS_FVE_CONV_WRITE_ERROR => Some("STATUS_FVE_CONV_WRITE_ERROR"), + SMB_NTSTATUS_FVE_OVERLAPPED_UPDATE => Some("STATUS_FVE_OVERLAPPED_UPDATE"), + SMB_NTSTATUS_FVE_FAILED_SECTOR_SIZE => Some("STATUS_FVE_FAILED_SECTOR_SIZE"), + SMB_NTSTATUS_FVE_FAILED_AUTHENTICATION => Some("STATUS_FVE_FAILED_AUTHENTICATION"), + SMB_NTSTATUS_FVE_NOT_OS_VOLUME => Some("STATUS_FVE_NOT_OS_VOLUME"), + SMB_NTSTATUS_FVE_KEYFILE_NOT_FOUND => Some("STATUS_FVE_KEYFILE_NOT_FOUND"), + SMB_NTSTATUS_FVE_KEYFILE_INVALID => Some("STATUS_FVE_KEYFILE_INVALID"), + SMB_NTSTATUS_FVE_KEYFILE_NO_VMK => Some("STATUS_FVE_KEYFILE_NO_VMK"), + SMB_NTSTATUS_FVE_TPM_DISABLED => Some("STATUS_FVE_TPM_DISABLED"), + SMB_NTSTATUS_FVE_TPM_SRK_AUTH_NOT_ZERO => Some("STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO"), + SMB_NTSTATUS_FVE_TPM_INVALID_PCR => Some("STATUS_FVE_TPM_INVALID_PCR"), + SMB_NTSTATUS_FVE_TPM_NO_VMK => Some("STATUS_FVE_TPM_NO_VMK"), + SMB_NTSTATUS_FVE_PIN_INVALID => Some("STATUS_FVE_PIN_INVALID"), + SMB_NTSTATUS_FVE_AUTH_INVALID_APPLICATION => Some("STATUS_FVE_AUTH_INVALID_APPLICATION"), + SMB_NTSTATUS_FVE_AUTH_INVALID_CONFIG => Some("STATUS_FVE_AUTH_INVALID_CONFIG"), + SMB_NTSTATUS_FVE_DEBUGGER_ENABLED => Some("STATUS_FVE_DEBUGGER_ENABLED"), + SMB_NTSTATUS_FVE_DRY_RUN_FAILED => Some("STATUS_FVE_DRY_RUN_FAILED"), + SMB_NTSTATUS_FVE_BAD_METADATA_POINTER => Some("STATUS_FVE_BAD_METADATA_POINTER"), + SMB_NTSTATUS_FVE_OLD_METADATA_COPY => Some("STATUS_FVE_OLD_METADATA_COPY"), + SMB_NTSTATUS_FVE_REBOOT_REQUIRED => Some("STATUS_FVE_REBOOT_REQUIRED"), + SMB_NTSTATUS_FVE_RAW_ACCESS => Some("STATUS_FVE_RAW_ACCESS"), + SMB_NTSTATUS_FVE_RAW_BLOCKED => Some("STATUS_FVE_RAW_BLOCKED"), + SMB_NTSTATUS_FVE_NO_FEATURE_LICENSE => Some("STATUS_FVE_NO_FEATURE_LICENSE"), + SMB_NTSTATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED => Some("STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED"), + SMB_NTSTATUS_FVE_CONV_RECOVERY_FAILED => Some("STATUS_FVE_CONV_RECOVERY_FAILED"), + SMB_NTSTATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG => Some("STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG"), + SMB_NTSTATUS_FVE_VOLUME_TOO_SMALL => Some("STATUS_FVE_VOLUME_TOO_SMALL"), + SMB_NTSTATUS_FWP_CALLOUT_NOT_FOUND => Some("STATUS_FWP_CALLOUT_NOT_FOUND"), + SMB_NTSTATUS_FWP_CONDITION_NOT_FOUND => Some("STATUS_FWP_CONDITION_NOT_FOUND"), + SMB_NTSTATUS_FWP_FILTER_NOT_FOUND => Some("STATUS_FWP_FILTER_NOT_FOUND"), + SMB_NTSTATUS_FWP_LAYER_NOT_FOUND => Some("STATUS_FWP_LAYER_NOT_FOUND"), + SMB_NTSTATUS_FWP_PROVIDER_NOT_FOUND => Some("STATUS_FWP_PROVIDER_NOT_FOUND"), + SMB_NTSTATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND => Some("STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND"), + SMB_NTSTATUS_FWP_SUBLAYER_NOT_FOUND => Some("STATUS_FWP_SUBLAYER_NOT_FOUND"), + SMB_NTSTATUS_FWP_NOT_FOUND => Some("STATUS_FWP_NOT_FOUND"), + SMB_NTSTATUS_FWP_ALREADY_EXISTS => Some("STATUS_FWP_ALREADY_EXISTS"), + SMB_NTSTATUS_FWP_IN_USE => Some("STATUS_FWP_IN_USE"), + SMB_NTSTATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS => Some("STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS"), + SMB_NTSTATUS_FWP_WRONG_SESSION => Some("STATUS_FWP_WRONG_SESSION"), + SMB_NTSTATUS_FWP_NO_TXN_IN_PROGRESS => Some("STATUS_FWP_NO_TXN_IN_PROGRESS"), + SMB_NTSTATUS_FWP_TXN_IN_PROGRESS => Some("STATUS_FWP_TXN_IN_PROGRESS"), + SMB_NTSTATUS_FWP_TXN_ABORTED => Some("STATUS_FWP_TXN_ABORTED"), + SMB_NTSTATUS_FWP_SESSION_ABORTED => Some("STATUS_FWP_SESSION_ABORTED"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_TXN => Some("STATUS_FWP_INCOMPATIBLE_TXN"), + SMB_NTSTATUS_FWP_TIMEOUT => Some("STATUS_FWP_TIMEOUT"), + SMB_NTSTATUS_FWP_NET_EVENTS_DISABLED => Some("STATUS_FWP_NET_EVENTS_DISABLED"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_LAYER => Some("STATUS_FWP_INCOMPATIBLE_LAYER"), + SMB_NTSTATUS_FWP_KM_CLIENTS_ONLY => Some("STATUS_FWP_KM_CLIENTS_ONLY"), + SMB_NTSTATUS_FWP_LIFETIME_MISMATCH => Some("STATUS_FWP_LIFETIME_MISMATCH"), + SMB_NTSTATUS_FWP_BUILTIN_OBJECT => Some("STATUS_FWP_BUILTIN_OBJECT"), + SMB_NTSTATUS_FWP_TOO_MANY_BOOTTIME_FILTERS => Some("STATUS_FWP_TOO_MANY_BOOTTIME_FILTERS"), + SMB_NTSTATUS_FWP_NOTIFICATION_DROPPED => Some("STATUS_FWP_NOTIFICATION_DROPPED"), + SMB_NTSTATUS_FWP_TRAFFIC_MISMATCH => Some("STATUS_FWP_TRAFFIC_MISMATCH"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_SA_STATE => Some("STATUS_FWP_INCOMPATIBLE_SA_STATE"), + SMB_NTSTATUS_FWP_NULL_POINTER => Some("STATUS_FWP_NULL_POINTER"), + SMB_NTSTATUS_FWP_INVALID_ENUMERATOR => Some("STATUS_FWP_INVALID_ENUMERATOR"), + SMB_NTSTATUS_FWP_INVALID_FLAGS => Some("STATUS_FWP_INVALID_FLAGS"), + SMB_NTSTATUS_FWP_INVALID_NET_MASK => Some("STATUS_FWP_INVALID_NET_MASK"), + SMB_NTSTATUS_FWP_INVALID_RANGE => Some("STATUS_FWP_INVALID_RANGE"), + SMB_NTSTATUS_FWP_INVALID_INTERVAL => Some("STATUS_FWP_INVALID_INTERVAL"), + SMB_NTSTATUS_FWP_ZERO_LENGTH_ARRAY => Some("STATUS_FWP_ZERO_LENGTH_ARRAY"), + SMB_NTSTATUS_FWP_NULL_DISPLAY_NAME => Some("STATUS_FWP_NULL_DISPLAY_NAME"), + SMB_NTSTATUS_FWP_INVALID_ACTION_TYPE => Some("STATUS_FWP_INVALID_ACTION_TYPE"), + SMB_NTSTATUS_FWP_INVALID_WEIGHT => Some("STATUS_FWP_INVALID_WEIGHT"), + SMB_NTSTATUS_FWP_MATCH_TYPE_MISMATCH => Some("STATUS_FWP_MATCH_TYPE_MISMATCH"), + SMB_NTSTATUS_FWP_TYPE_MISMATCH => Some("STATUS_FWP_TYPE_MISMATCH"), + SMB_NTSTATUS_FWP_OUT_OF_BOUNDS => Some("STATUS_FWP_OUT_OF_BOUNDS"), + SMB_NTSTATUS_FWP_RESERVED => Some("STATUS_FWP_RESERVED"), + SMB_NTSTATUS_FWP_DUPLICATE_CONDITION => Some("STATUS_FWP_DUPLICATE_CONDITION"), + SMB_NTSTATUS_FWP_DUPLICATE_KEYMOD => Some("STATUS_FWP_DUPLICATE_KEYMOD"), + SMB_NTSTATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER => Some("STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER"), + SMB_NTSTATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER => Some("STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER"), + SMB_NTSTATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER => Some("STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER"), + SMB_NTSTATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT => Some("STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_AUTH_METHOD => Some("STATUS_FWP_INCOMPATIBLE_AUTH_METHOD"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_DH_GROUP => Some("STATUS_FWP_INCOMPATIBLE_DH_GROUP"), + SMB_NTSTATUS_FWP_EM_NOT_SUPPORTED => Some("STATUS_FWP_EM_NOT_SUPPORTED"), + SMB_NTSTATUS_FWP_NEVER_MATCH => Some("STATUS_FWP_NEVER_MATCH"), + SMB_NTSTATUS_FWP_PROVIDER_CONTEXT_MISMATCH => Some("STATUS_FWP_PROVIDER_CONTEXT_MISMATCH"), + SMB_NTSTATUS_FWP_INVALID_PARAMETER => Some("STATUS_FWP_INVALID_PARAMETER"), + SMB_NTSTATUS_FWP_TOO_MANY_SUBLAYERS => Some("STATUS_FWP_TOO_MANY_SUBLAYERS"), + SMB_NTSTATUS_FWP_CALLOUT_NOTIFICATION_FAILED => Some("STATUS_FWP_CALLOUT_NOTIFICATION_FAILED"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_AUTH_CONFIG => Some("STATUS_FWP_INCOMPATIBLE_AUTH_CONFIG"), + SMB_NTSTATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG => Some("STATUS_FWP_INCOMPATIBLE_CIPHER_CONFIG"), + SMB_NTSTATUS_FWP_DUPLICATE_AUTH_METHOD => Some("STATUS_FWP_DUPLICATE_AUTH_METHOD"), + SMB_NTSTATUS_FWP_TCPIP_NOT_READY => Some("STATUS_FWP_TCPIP_NOT_READY"), + SMB_NTSTATUS_FWP_INJECT_HANDLE_CLOSING => Some("STATUS_FWP_INJECT_HANDLE_CLOSING"), + SMB_NTSTATUS_FWP_INJECT_HANDLE_STALE => Some("STATUS_FWP_INJECT_HANDLE_STALE"), + SMB_NTSTATUS_FWP_CANNOT_PEND => Some("STATUS_FWP_CANNOT_PEND"), + SMB_NTSTATUS_NDIS_CLOSING => Some("STATUS_NDIS_CLOSING"), + SMB_NTSTATUS_NDIS_BAD_VERSION => Some("STATUS_NDIS_BAD_VERSION"), + SMB_NTSTATUS_NDIS_BAD_CHARACTERISTICS => Some("STATUS_NDIS_BAD_CHARACTERISTICS"), + SMB_NTSTATUS_NDIS_ADAPTER_NOT_FOUND => Some("STATUS_NDIS_ADAPTER_NOT_FOUND"), + SMB_NTSTATUS_NDIS_OPEN_FAILED => Some("STATUS_NDIS_OPEN_FAILED"), + SMB_NTSTATUS_NDIS_DEVICE_FAILED => Some("STATUS_NDIS_DEVICE_FAILED"), + SMB_NTSTATUS_NDIS_MULTICAST_FULL => Some("STATUS_NDIS_MULTICAST_FULL"), + SMB_NTSTATUS_NDIS_MULTICAST_EXISTS => Some("STATUS_NDIS_MULTICAST_EXISTS"), + SMB_NTSTATUS_NDIS_MULTICAST_NOT_FOUND => Some("STATUS_NDIS_MULTICAST_NOT_FOUND"), + SMB_NTSTATUS_NDIS_REQUEST_ABORTED => Some("STATUS_NDIS_REQUEST_ABORTED"), + SMB_NTSTATUS_NDIS_RESET_IN_PROGRESS => Some("STATUS_NDIS_RESET_IN_PROGRESS"), + SMB_NTSTATUS_NDIS_INVALID_PACKET => Some("STATUS_NDIS_INVALID_PACKET"), + SMB_NTSTATUS_NDIS_INVALID_DEVICE_REQUEST => Some("STATUS_NDIS_INVALID_DEVICE_REQUEST"), + SMB_NTSTATUS_NDIS_ADAPTER_NOT_READY => Some("STATUS_NDIS_ADAPTER_NOT_READY"), + SMB_NTSTATUS_NDIS_INVALID_LENGTH => Some("STATUS_NDIS_INVALID_LENGTH"), + SMB_NTSTATUS_NDIS_INVALID_DATA => Some("STATUS_NDIS_INVALID_DATA"), + SMB_NTSTATUS_NDIS_BUFFER_TOO_SHORT => Some("STATUS_NDIS_BUFFER_TOO_SHORT"), + SMB_NTSTATUS_NDIS_INVALID_OID => Some("STATUS_NDIS_INVALID_OID"), + SMB_NTSTATUS_NDIS_ADAPTER_REMOVED => Some("STATUS_NDIS_ADAPTER_REMOVED"), + SMB_NTSTATUS_NDIS_UNSUPPORTED_MEDIA => Some("STATUS_NDIS_UNSUPPORTED_MEDIA"), + SMB_NTSTATUS_NDIS_GROUP_ADDRESS_IN_USE => Some("STATUS_NDIS_GROUP_ADDRESS_IN_USE"), + SMB_NTSTATUS_NDIS_FILE_NOT_FOUND => Some("STATUS_NDIS_FILE_NOT_FOUND"), + SMB_NTSTATUS_NDIS_ERROR_READING_FILE => Some("STATUS_NDIS_ERROR_READING_FILE"), + SMB_NTSTATUS_NDIS_ALREADY_MAPPED => Some("STATUS_NDIS_ALREADY_MAPPED"), + SMB_NTSTATUS_NDIS_RESOURCE_CONFLICT => Some("STATUS_NDIS_RESOURCE_CONFLICT"), + SMB_NTSTATUS_NDIS_MEDIA_DISCONNECTED => Some("STATUS_NDIS_MEDIA_DISCONNECTED"), + SMB_NTSTATUS_NDIS_INVALID_ADDRESS => Some("STATUS_NDIS_INVALID_ADDRESS"), + SMB_NTSTATUS_NDIS_PAUSED => Some("STATUS_NDIS_PAUSED"), + SMB_NTSTATUS_NDIS_INTERFACE_NOT_FOUND => Some("STATUS_NDIS_INTERFACE_NOT_FOUND"), + SMB_NTSTATUS_NDIS_UNSUPPORTED_REVISION => Some("STATUS_NDIS_UNSUPPORTED_REVISION"), + SMB_NTSTATUS_NDIS_INVALID_PORT => Some("STATUS_NDIS_INVALID_PORT"), + SMB_NTSTATUS_NDIS_INVALID_PORT_STATE => Some("STATUS_NDIS_INVALID_PORT_STATE"), + SMB_NTSTATUS_NDIS_LOW_POWER_STATE => Some("STATUS_NDIS_LOW_POWER_STATE"), + SMB_NTSTATUS_NDIS_NOT_SUPPORTED => Some("STATUS_NDIS_NOT_SUPPORTED"), + SMB_NTSTATUS_NDIS_OFFLOAD_POLICY => Some("STATUS_NDIS_OFFLOAD_POLICY"), + SMB_NTSTATUS_NDIS_OFFLOAD_CONNECTION_REJECTED => Some("STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED"), + SMB_NTSTATUS_NDIS_OFFLOAD_PATH_REJECTED => Some("STATUS_NDIS_OFFLOAD_PATH_REJECTED"), + SMB_NTSTATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED => Some("STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED"), + SMB_NTSTATUS_NDIS_DOT11_MEDIA_IN_USE => Some("STATUS_NDIS_DOT11_MEDIA_IN_USE"), + SMB_NTSTATUS_NDIS_DOT11_POWER_STATE_INVALID => Some("STATUS_NDIS_DOT11_POWER_STATE_INVALID"), + SMB_NTSTATUS_NDIS_PM_WOL_PATTERN_LIST_FULL => Some("STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL"), + SMB_NTSTATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL => Some("STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL"), + SMB_NTSTATUS_IPSEC_BAD_SPI => Some("STATUS_IPSEC_BAD_SPI"), + SMB_NTSTATUS_IPSEC_SA_LIFETIME_EXPIRED => Some("STATUS_IPSEC_SA_LIFETIME_EXPIRED"), + SMB_NTSTATUS_IPSEC_WRONG_SA => Some("STATUS_IPSEC_WRONG_SA"), + SMB_NTSTATUS_IPSEC_REPLAY_CHECK_FAILED => Some("STATUS_IPSEC_REPLAY_CHECK_FAILED"), + SMB_NTSTATUS_IPSEC_INVALID_PACKET => Some("STATUS_IPSEC_INVALID_PACKET"), + SMB_NTSTATUS_IPSEC_INTEGRITY_CHECK_FAILED => Some("STATUS_IPSEC_INTEGRITY_CHECK_FAILED"), + SMB_NTSTATUS_IPSEC_CLEAR_TEXT_DROP => Some("STATUS_IPSEC_CLEAR_TEXT_DROP"), + SMB_NTSTATUS_IPSEC_AUTH_FIREWALL_DROP => Some("STATUS_IPSEC_AUTH_FIREWALL_DROP"), + SMB_NTSTATUS_IPSEC_THROTTLE_DROP => Some("STATUS_IPSEC_THROTTLE_DROP"), + SMB_NTSTATUS_IPSEC_DOSP_BLOCK => Some("STATUS_IPSEC_DOSP_BLOCK"), + SMB_NTSTATUS_IPSEC_DOSP_RECEIVED_MULTICAST => Some("STATUS_IPSEC_DOSP_RECEIVED_MULTICAST"), + SMB_NTSTATUS_IPSEC_DOSP_INVALID_PACKET => Some("STATUS_IPSEC_DOSP_INVALID_PACKET"), + SMB_NTSTATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED => Some("STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED"), + SMB_NTSTATUS_IPSEC_DOSP_MAX_ENTRIES => Some("STATUS_IPSEC_DOSP_MAX_ENTRIES"), + SMB_NTSTATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED => Some("STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED"), + SMB_NTSTATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES => Some("STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES"), + SMB_NTSTATUS_VOLMGR_MIRROR_NOT_SUPPORTED => Some("STATUS_VOLMGR_MIRROR_NOT_SUPPORTED"), + SMB_NTSTATUS_VOLMGR_RAID5_NOT_SUPPORTED => Some("STATUS_VOLMGR_RAID5_NOT_SUPPORTED"), + SMB_NTSTATUS_VIRTDISK_PROVIDER_NOT_FOUND => Some("STATUS_VIRTDISK_PROVIDER_NOT_FOUND"), + SMB_NTSTATUS_VIRTDISK_NOT_VIRTUAL_DISK => Some("STATUS_VIRTDISK_NOT_VIRTUAL_DISK"), + SMB_NTSTATUS_VHD_PARENT_VHD_ACCESS_DENIED => Some("STATUS_VHD_PARENT_VHD_ACCESS_DENIED"), + SMB_NTSTATUS_VHD_CHILD_PARENT_SIZE_MISMATCH => Some("STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH"), + SMB_NTSTATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED => Some("STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED"), + SMB_NTSTATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT => Some("STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT"), + + _ => None, + } +} + diff --git a/rust/src/snmp/detect.rs b/rust/src/snmp/detect.rs new file mode 100644 index 0000000..bb4ffd1 --- /dev/null +++ b/rust/src/snmp/detect.rs @@ -0,0 +1,58 @@ +/* Copyright (C) 2017-2019 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 Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::snmp::snmp::SNMPTransaction; + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_tx_get_version(tx: &mut SNMPTransaction, version: *mut u32) { + debug_assert!(tx.version != 0, "SNMP version is 0"); + *version = tx.version; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_tx_get_community( + tx: &mut SNMPTransaction, buf: *mut *const u8, len: *mut u32, +) { + if let Some(ref c) = tx.community { + *buf = c.as_ptr(); + *len = c.len() as u32; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_tx_get_pdu_type(tx: &mut SNMPTransaction, pdu_type: *mut u32) { + match tx.info { + Some(ref info) => { + *pdu_type = info.pdu_type.0; + } + None => { + *pdu_type = 0xffffffff; + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_tx_get_usm( + tx: &mut SNMPTransaction, buf: *mut *const u8, len: *mut u32, +) { + if let Some(ref c) = tx.usm { + *buf = c.as_ptr(); + *len = c.len() as u32; + } +} diff --git a/rust/src/snmp/log.rs b/rust/src/snmp/log.rs new file mode 100644 index 0000000..8341481 --- /dev/null +++ b/rust/src/snmp/log.rs @@ -0,0 +1,81 @@ +/* Copyright (C) 2018-2019 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 Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::snmp::snmp::SNMPTransaction; +use crate::snmp::snmp_parser::{NetworkAddress,PduType}; +use std::borrow::Cow; + +fn str_of_pdu_type(t:&PduType) -> Cow<str> { + match t { + &PduType::GetRequest => Cow::Borrowed("get_request"), + &PduType::GetNextRequest => Cow::Borrowed("get_next_request"), + &PduType::Response => Cow::Borrowed("response"), + &PduType::SetRequest => Cow::Borrowed("set_request"), + &PduType::TrapV1 => Cow::Borrowed("trap_v1"), + &PduType::GetBulkRequest => Cow::Borrowed("get_bulk_request"), + &PduType::InformRequest => Cow::Borrowed("inform_request"), + &PduType::TrapV2 => Cow::Borrowed("trap_v2"), + &PduType::Report => Cow::Borrowed("report"), + x => Cow::Owned(format!("Unknown(0x{:x})", x.0)), + } +} + +fn snmp_log_response(jsb: &mut JsonBuilder, tx: &mut SNMPTransaction) -> Result<(), JsonError> +{ + jsb.set_uint("version", tx.version as u64)?; + if tx.encrypted { + jsb.set_string("pdu_type", "encrypted")?; + } else { + if let Some(ref info) = tx.info { + jsb.set_string("pdu_type", &str_of_pdu_type(&info.pdu_type))?; + if info.err.0 != 0 { + jsb.set_string("error", &format!("{:?}", info.err))?; + } + if let Some((trap_type, ref oid, address)) = info.trap_type { + jsb.set_string("trap_type", &format!("{:?}", trap_type))?; + jsb.set_string("trap_oid", &oid.to_string())?; + match address { + NetworkAddress::IPv4(ip) => {jsb.set_string("trap_address", &ip.to_string())?;}, + } + } + if !info.vars.is_empty() { + jsb.open_array("vars")?; + for var in info.vars.iter() { + jsb.append_string(&var.to_string())?; + } + jsb.close()?; + } + } + if let Some(community) = &tx.community { + jsb.set_string("community", community)?; + } + if let Some(usm) = &tx.usm { + jsb.set_string("usm", usm)?; + } + } + + return Ok(()); +} + +#[no_mangle] +pub extern "C" fn rs_snmp_log_json_response(jsb: &mut JsonBuilder, tx: &mut SNMPTransaction) -> bool +{ + snmp_log_response(jsb, tx).is_ok() +} diff --git a/rust/src/snmp/mod.rs b/rust/src/snmp/mod.rs new file mode 100644 index 0000000..7c6ceb3 --- /dev/null +++ b/rust/src/snmp/mod.rs @@ -0,0 +1,26 @@ +/* Copyright (C) 2017-2019 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. + */ + +//! SNMP application layer, parser, detection and logger module. + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +extern crate snmp_parser; + +pub mod snmp; +pub mod log; +pub mod detect; diff --git a/rust/src/snmp/snmp.rs b/rust/src/snmp/snmp.rs new file mode 100644 index 0000000..a4481f4 --- /dev/null +++ b/rust/src/snmp/snmp.rs @@ -0,0 +1,429 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::snmp::snmp_parser::*; +use crate::core::{self, *}; +use crate::applayer::{self, *}; +use std; +use std::ffi::CString; + +use asn1_rs::Oid; +use der_parser::ber::BerObjectContent; +use der_parser::der::parse_der_sequence; +use nom7::{Err, IResult}; +use nom7::error::{ErrorKind, make_error}; + +#[derive(AppLayerEvent)] +pub enum SNMPEvent { + MalformedData, + UnknownSecurityModel, + VersionMismatch, +} + +#[derive(Default)] +pub struct SNMPState<'a> { + state_data: AppLayerStateData, + + /// SNMP protocol version + pub version: u32, + + /// List of transactions for this session + transactions: Vec<SNMPTransaction<'a>>, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, +} + +pub struct SNMPPduInfo<'a> { + pub pdu_type: PduType, + + pub err: ErrorStatus, + + pub trap_type: Option<(TrapType,Oid<'a>,NetworkAddress)>, + + pub vars: Vec<Oid<'a>>, +} + +pub struct SNMPTransaction<'a> { + /// PDU version + pub version: u32, + + /// PDU info, if present (and cleartext) + pub info: Option<SNMPPduInfo<'a>>, + + /// Community, if present (SNMPv2) + pub community: Option<String>, + + /// USM info, if present (SNMPv3) + pub usm: Option<String>, + + /// True if transaction was encrypted + pub encrypted: bool, + + /// The internal transaction id + id: u64, + + tx_data: applayer::AppLayerTxData, +} + +impl<'a> Transaction for SNMPTransaction<'a> { + fn id(&self) -> u64 { + self.id + } +} + +impl<'a> SNMPState<'a> { + pub fn new() -> SNMPState<'a> { + Default::default() + } +} + +impl<'a> Default for SNMPPduInfo<'a> { + fn default() -> SNMPPduInfo<'a> { + SNMPPduInfo{ + pdu_type: PduType(0), + err: ErrorStatus::NoError, + trap_type: None, + vars: Vec::new() + } + } +} + +impl<'a> State<SNMPTransaction<'a>> for SNMPState<'a> { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&SNMPTransaction<'a>> { + self.transactions.get(index) + } +} + +impl<'a> SNMPState<'a> { + fn add_pdu_info(&mut self, pdu: &SnmpPdu<'a>, tx: &mut SNMPTransaction<'a>) { + let mut pdu_info = SNMPPduInfo { + pdu_type: pdu.pdu_type(), + ..Default::default() + }; + match *pdu { + SnmpPdu::Generic(ref pdu) => { + pdu_info.err = pdu.err; + }, + SnmpPdu::Bulk(_) => { + }, + SnmpPdu::TrapV1(ref t) => { + pdu_info.trap_type = Some((t.generic_trap,t.enterprise.clone(),t.agent_addr)); + } + } + + for var in pdu.vars_iter() { + pdu_info.vars.push(var.oid.to_owned()); + } + tx.info = Some(pdu_info); + } + + fn handle_snmp_v12(&mut self, msg: SnmpMessage<'a>, _direction: Direction) -> i32 { + let mut tx = self.new_tx(_direction); + // in the message, version is encoded as 0 (version 1) or 1 (version 2) + if self.version != msg.version + 1 { + SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version+1); + self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch); + } + self.add_pdu_info(&msg.pdu, &mut tx); + tx.community = Some(msg.community); + self.transactions.push(tx); + 0 + } + + fn handle_snmp_v3(&mut self, msg: SnmpV3Message<'a>, _direction: Direction) -> i32 { + let mut tx = self.new_tx(_direction); + if self.version != msg.version { + SCLogDebug!("SNMP version mismatch: expected {}, received {}", self.version, msg.version); + self.set_event_tx(&mut tx, SNMPEvent::VersionMismatch); + } + match msg.data { + ScopedPduData::Plaintext(pdu) => { + self.add_pdu_info(&pdu.data, &mut tx); + }, + _ => { + tx.encrypted = true; + } + } + match msg.security_params { + SecurityParameters::USM(usm) => { + tx.usm = Some(usm.msg_user_name); + }, + _ => { + self.set_event_tx(&mut tx, SNMPEvent::UnknownSecurityModel); + } + } + self.transactions.push(tx); + 0 + } + + /// Parse an SNMP request message + /// + /// Returns 0 if successful, or -1 on error + fn parse(&mut self, i: &'a [u8], direction: Direction) -> i32 { + if self.version == 0 { + if let Ok((_, x)) = parse_pdu_envelope_version(i) { + self.version = x; + } + } + match parse_snmp_generic_message(i) { + Ok((_rem,SnmpGenericMessage::V1(msg))) | + Ok((_rem,SnmpGenericMessage::V2(msg))) => self.handle_snmp_v12(msg, direction), + Ok((_rem,SnmpGenericMessage::V3(msg))) => self.handle_snmp_v3(msg, direction), + Err(_e) => { + SCLogDebug!("parse_snmp failed: {:?}", _e); + self.set_event(SNMPEvent::MalformedData); + -1 + }, + } + } + + fn free(&mut self) { + // All transactions are freed when the `transactions` object is freed. + // But let's be explicit + self.transactions.clear(); + } + + fn new_tx(&mut self, direction: Direction) -> SNMPTransaction<'a> { + self.tx_id += 1; + SNMPTransaction::new(direction, self.version, self.tx_id) + } + + fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&SNMPTransaction> { + self.transactions.iter().rev().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } + + /// Set an event. The event is set on the most recent transaction. + fn set_event(&mut self, event: SNMPEvent) { + if let Some(tx) = self.transactions.last_mut() { + tx.tx_data.set_event(event as u8); + } + } + + /// Set an event on a specific transaction. + fn set_event_tx(&self, tx: &mut SNMPTransaction, event: SNMPEvent) { + tx.tx_data.set_event(event as u8); + } +} + +impl<'a> SNMPTransaction<'a> { + pub fn new(direction: Direction, version: u32, id: u64) -> SNMPTransaction<'a> { + SNMPTransaction { + version, + info: None, + community: None, + usm: None, + encrypted: false, + id, + tx_data: applayer::AppLayerTxData::for_direction(direction), + } + } +} + +/// Returns *mut SNMPState +#[no_mangle] +pub extern "C" fn rs_snmp_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = SNMPState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +/// Params: +/// - state: *mut SNMPState as void pointer +#[no_mangle] +pub extern "C" fn rs_snmp_state_free(state: *mut std::os::raw::c_void) { + let mut snmp_state = unsafe{ Box::from_raw(state as *mut SNMPState) }; + snmp_state.free(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_parse_request(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,SNMPState); + state.parse(stream_slice.as_slice(), Direction::ToServer).into() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_parse_response(_flow: *const core::Flow, + state: *mut std::os::raw::c_void, + _pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void, + ) -> AppLayerResult { + let state = cast_pointer!(state,SNMPState); + state.parse(stream_slice.as_slice(), Direction::ToClient).into() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_state_get_tx(state: *mut std::os::raw::c_void, + tx_id: u64) + -> *mut std::os::raw::c_void +{ + let state = cast_pointer!(state,SNMPState); + match state.get_tx_by_id(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_state_get_tx_count(state: *mut std::os::raw::c_void) + -> u64 +{ + let state = cast_pointer!(state,SNMPState); + state.tx_id +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_state_tx_free(state: *mut std::os::raw::c_void, + tx_id: u64) +{ + let state = cast_pointer!(state,SNMPState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_snmp_tx_get_alstate_progress(_tx: *mut std::os::raw::c_void, + _direction: u8) + -> std::os::raw::c_int +{ + 1 +} + +static mut ALPROTO_SNMP : AppProto = ALPROTO_UNKNOWN; + +// Read PDU sequence and extract version, if similar to SNMP definition +fn parse_pdu_envelope_version(i:&[u8]) -> IResult<&[u8],u32> { + match parse_der_sequence(i) { + Ok((_,x)) => { + #[allow(clippy::single_match)] + match x.content { + BerObjectContent::Sequence(ref v) => { + if v.len() == 3 { + match v[0].as_u32() { + Ok(0) => { return Ok((i,1)); }, // possibly SNMPv1 + Ok(1) => { return Ok((i,2)); }, // possibly SNMPv2c + _ => () + } + } else if v.len() == 4 && v[0].as_u32() == Ok(3) { + return Ok((i,3)); // possibly SNMPv3 + } + }, + _ => () + }; + Err(Err::Error(make_error(i, ErrorKind::Verify))) + }, + Err(Err::Incomplete(i)) => Err(Err::Incomplete(i)), + Err(Err::Failure(_)) | + Err(Err::Error(_)) => Err(Err::Error(make_error(i,ErrorKind::Verify))) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_snmp_probing_parser(_flow: *const Flow, + _direction: u8, + input:*const u8, + input_len: u32, + _rdir: *mut u8) -> AppProto { + let slice = build_slice!(input,input_len as usize); + let alproto = ALPROTO_SNMP; + if slice.len() < 4 { return ALPROTO_FAILED; } + match parse_pdu_envelope_version(slice) { + Ok((_,_)) => alproto, + Err(Err::Incomplete(_)) => ALPROTO_UNKNOWN, + _ => ALPROTO_FAILED, + } +} + +export_tx_data_get!(rs_snmp_get_tx_data, SNMPTransaction); +export_state_data_get!(rs_snmp_get_state_data, SNMPState); + +const PARSER_NAME : &[u8] = b"snmp\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_register_snmp_parser() { + let default_port = CString::new("161").unwrap(); + let mut parser = RustParser { + name : PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port : default_port.as_ptr(), + ipproto : core::IPPROTO_UDP, + probe_ts : Some(rs_snmp_probing_parser), + probe_tc : Some(rs_snmp_probing_parser), + min_depth : 0, + max_depth : 16, + state_new : rs_snmp_state_new, + state_free : rs_snmp_state_free, + tx_free : rs_snmp_state_tx_free, + parse_ts : rs_snmp_parse_request, + parse_tc : rs_snmp_parse_response, + get_tx_count : rs_snmp_state_get_tx_count, + get_tx : rs_snmp_state_get_tx, + tx_comp_st_ts : 1, + tx_comp_st_tc : 1, + tx_get_progress : rs_snmp_tx_get_alstate_progress, + get_eventinfo : Some(SNMPEvent::get_event_info), + get_eventinfo_byid : Some(SNMPEvent::get_event_info_by_id), + localstorage_new : None, + localstorage_free : None, + get_tx_files : None, + get_tx_iterator : Some(applayer::state_get_tx_iterator::<SNMPState, SNMPTransaction>), + get_tx_data : rs_snmp_get_tx_data, + get_state_data : rs_snmp_get_state_data, + apply_tx_config : None, + flags : 0, + truncate : None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + let ip_proto_str = CString::new("udp").unwrap(); + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + // port 161 + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + // store the allocated ID for the probe function + ALPROTO_SNMP = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + // port 162 + let default_port_traps = CString::new("162").unwrap(); + parser.default_port = default_port_traps.as_ptr(); + let _ = AppLayerRegisterProtocolDetection(&parser, 1); + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + } else { + SCLogDebug!("Protocol detector and parser disabled for SNMP."); + } +} diff --git a/rust/src/ssh/detect.rs b/rust/src/ssh/detect.rs new file mode 100644 index 0000000..6aa18cd --- /dev/null +++ b/rust/src/ssh/detect.rs @@ -0,0 +1,142 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::ssh::SSHTransaction; +use crate::core::Direction; +use std::ptr; + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_protocol( + tx: *mut std::os::raw::c_void, buffer: *mut *const u8, buffer_len: *mut u32, direction: u8, +) -> u8 { + let tx = cast_pointer!(tx, SSHTransaction); + match direction.into() { + Direction::ToServer => { + let m = &tx.cli_hdr.protover; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + Direction::ToClient => { + let m = &tx.srv_hdr.protover; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + } + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_software( + tx: *mut std::os::raw::c_void, buffer: *mut *const u8, buffer_len: *mut u32, direction: u8, +) -> u8 { + let tx = cast_pointer!(tx, SSHTransaction); + match direction.into() { + Direction::ToServer => { + let m = &tx.cli_hdr.swver; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + Direction::ToClient => { + let m = &tx.srv_hdr.swver; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + } + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_hassh( + tx: *mut std::os::raw::c_void, + buffer: *mut *const u8, + buffer_len: *mut u32, + direction: u8, +) -> u8 { + let tx = cast_pointer!(tx, SSHTransaction); + match direction.into() { + Direction::ToServer => { + let m = &tx.cli_hdr.hassh; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + Direction::ToClient => { + let m = &tx.srv_hdr.hassh; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + } + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_hassh_string( + tx: *mut std::os::raw::c_void, + buffer: *mut *const u8, + buffer_len: *mut u32, + direction: u8, +) -> u8 { + let tx = cast_pointer!(tx, SSHTransaction); + match direction.into() { + Direction::ToServer => { + let m = &tx.cli_hdr.hassh_string; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + Direction::ToClient => { + let m = &tx.srv_hdr.hassh_string; + if !m.is_empty() { + *buffer = m.as_ptr(); + *buffer_len = m.len() as u32; + return 1; + } + } + } + *buffer = ptr::null(); + *buffer_len = 0; + + return 0; +} diff --git a/rust/src/ssh/logger.rs b/rust/src/ssh/logger.rs new file mode 100644 index 0000000..9bc7d7c --- /dev/null +++ b/rust/src/ssh/logger.rs @@ -0,0 +1,71 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::ssh::SSHTransaction; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +fn log_ssh(tx: &SSHTransaction, js: &mut JsonBuilder) -> Result<bool, JsonError> { + if tx.cli_hdr.protover.is_empty() && tx.srv_hdr.protover.is_empty() { + return Ok(false); + } + if !tx.cli_hdr.protover.is_empty() { + js.open_object("client")?; + js.set_string_from_bytes("proto_version", &tx.cli_hdr.protover)?; + if !tx.cli_hdr.swver.is_empty() { + js.set_string_from_bytes("software_version", &tx.cli_hdr.swver)?; + } + if !tx.cli_hdr.hassh.is_empty() || !tx.cli_hdr.hassh_string.is_empty() { + js.open_object("hassh")?; + if !tx.cli_hdr.hassh.is_empty() { + js.set_string_from_bytes("hash", &tx.cli_hdr.hassh)?; + } + if !tx.cli_hdr.hassh_string.is_empty() { + js.set_string_from_bytes("string", &tx.cli_hdr.hassh_string)?; + } + js.close()?; + } + js.close()?; + } + if !tx.srv_hdr.protover.is_empty() { + js.open_object("server")?; + js.set_string_from_bytes("proto_version", &tx.srv_hdr.protover)?; + if !tx.srv_hdr.swver.is_empty() { + js.set_string_from_bytes("software_version", &tx.srv_hdr.swver)?; + } + if !tx.srv_hdr.hassh.is_empty() || !tx.srv_hdr.hassh_string.is_empty() { + js.open_object("hassh")?; + if !tx.srv_hdr.hassh.is_empty() { + js.set_string_from_bytes("hash", &tx.srv_hdr.hassh)?; + } + if !tx.srv_hdr.hassh_string.is_empty() { + js.set_string_from_bytes("string", &tx.srv_hdr.hassh_string)?; + } + js.close()?; + } + js.close()?; + } + return Ok(true); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_log_json(tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool { + let tx = cast_pointer!(tx, SSHTransaction); + if let Ok(x) = log_ssh(tx, js) { + return x; + } + return false; +} diff --git a/rust/src/ssh/mod.rs b/rust/src/ssh/mod.rs new file mode 100644 index 0000000..ff506e9 --- /dev/null +++ b/rust/src/ssh/mod.rs @@ -0,0 +1,23 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! SSH application layer, logger, detection and parser module. + +pub mod detect; +pub mod logger; +mod parser; +pub mod ssh; diff --git a/rust/src/ssh/parser.rs b/rust/src/ssh/parser.rs new file mode 100644 index 0000000..bfad8c0 --- /dev/null +++ b/rust/src/ssh/parser.rs @@ -0,0 +1,733 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use digest::Digest; +use digest::Update; +use md5::Md5; +use nom7::branch::alt; +use nom7::bytes::streaming::{is_not, tag, take, take_while}; +use nom7::character::streaming::char; +use nom7::combinator::{complete, eof, not, rest, verify}; +use nom7::multi::length_data; +use nom7::number::streaming::{be_u32, be_u8}; +use nom7::sequence::terminated; +use nom7::IResult; +use std::fmt; + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub enum MessageCode { + Disconnect, + Ignore, + Unimplemented, + Debug, + ServiceRequest, + ServiceAccept, + Kexinit, + NewKeys, + KexdhInit, + KexdhReply, + + Undefined(u8), +} + +impl MessageCode { + fn from_u8(value: u8) -> MessageCode { + match value { + 1 => MessageCode::Disconnect, + 2 => MessageCode::Ignore, + 3 => MessageCode::Unimplemented, + 4 => MessageCode::Debug, + 5 => MessageCode::ServiceRequest, + 6 => MessageCode::ServiceAccept, + 20 => MessageCode::Kexinit, + 21 => MessageCode::NewKeys, + 30 => MessageCode::KexdhInit, + 31 => MessageCode::KexdhReply, + _ => MessageCode::Undefined(value), + } + } +} + +#[inline] +fn is_not_lineend(b: u8) -> bool { + if b == 10 || b == 13 { + return false; + } + return true; +} + +//may leave \r at the end to be removed +pub fn ssh_parse_line(i: &[u8]) -> IResult<&[u8], &[u8]> { + fn parser(i: &[u8]) -> IResult<&[u8], &[u8]> { + let (i, bytes) = tag("\r")(i)?; + let (i, _) = not(eof)(i)?; + Ok((i, bytes)) + } + terminated( + take_while(is_not_lineend), + alt(( tag("\n"), tag("\r\n"), parser + )) + )(i) +} + +#[derive(PartialEq, Eq)] +pub struct SshBanner<'a> { + pub protover: &'a [u8], + pub swver: &'a [u8], +} + +// Could be simplified adding dummy \n at the end +// or use nom5 nom::bytes::complete::is_not +pub fn ssh_parse_banner(i: &[u8]) -> IResult<&[u8], SshBanner> { + let (i, _) = tag("SSH-")(i)?; + let (i, protover) = is_not("-")(i)?; + let (i, _) = char('-')(i)?; + let (i, swver) = alt((complete(is_not(" \r\n")), rest))(i)?; + //remaining after space is comments + Ok((i, SshBanner { protover, swver })) +} + +#[derive(PartialEq, Eq)] +pub struct SshRecordHeader { + pub pkt_len: u32, + padding_len: u8, + pub msg_code: MessageCode, +} + +impl fmt::Display for SshRecordHeader { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "(pkt_len:{}, padding_len:{}, msg_code:{:?})", + self.pkt_len, self.padding_len, self.msg_code + ) + } +} + +pub fn ssh_parse_record_header(i: &[u8]) -> IResult<&[u8], SshRecordHeader> { + let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?; + let (i, padding_len) = be_u8(i)?; + let (i, msg_code) = be_u8(i)?; + Ok(( + i, + SshRecordHeader { + pkt_len, + padding_len, + msg_code: MessageCode::from_u8(msg_code), + }, + )) +} + +//test for evasion against pkt_len=0or1... +pub fn ssh_parse_record(i: &[u8]) -> IResult<&[u8], SshRecordHeader> { + let (i, pkt_len) = verify(be_u32, |&val| val > 1)(i)?; + let (i, padding_len) = be_u8(i)?; + let (i, msg_code) = be_u8(i)?; + let (i, _) = take((pkt_len - 2) as usize)(i)?; + Ok(( + i, + SshRecordHeader { + pkt_len, + padding_len, + msg_code: MessageCode::from_u8(msg_code), + }, + )) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct SshPacketKeyExchange<'a> { + pub cookie: &'a [u8], + pub kex_algs: &'a [u8], + pub server_host_key_algs: &'a [u8], + pub encr_algs_client_to_server: &'a [u8], + pub encr_algs_server_to_client: &'a [u8], + pub mac_algs_client_to_server: &'a [u8], + pub mac_algs_server_to_client: &'a [u8], + pub comp_algs_client_to_server: &'a [u8], + pub comp_algs_server_to_client: &'a [u8], + pub langs_client_to_server: &'a [u8], + pub langs_server_to_client: &'a [u8], + pub first_kex_packet_follows: u8, + pub reserved: u32, +} + +const SSH_HASSH_STRING_DELIMITER_SLICE: [u8; 1] = [b';']; + +impl<'a> SshPacketKeyExchange<'a> { + pub fn generate_hassh( + &self, hassh_string: &mut Vec<u8>, hassh: &mut Vec<u8>, to_server: &bool, + ) { + let slices = if *to_server { + [ + self.kex_algs, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.encr_algs_server_to_client, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.mac_algs_server_to_client, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.comp_algs_server_to_client, + ] + } else { + [ + self.kex_algs, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.encr_algs_client_to_server, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.mac_algs_client_to_server, + &SSH_HASSH_STRING_DELIMITER_SLICE, + self.comp_algs_client_to_server, + ] + }; + // reserving memory + hassh_string.reserve_exact(slices.iter().fold(0, |acc, x| acc + x.len())); + // copying slices to hassh string + slices + .iter() + .for_each(|&x| hassh_string.extend_from_slice(x)); + hassh.extend(format!("{:x}", Md5::new().chain(&hassh_string).finalize()).as_bytes()); + } +} + +#[inline] +fn parse_string(i: &[u8]) -> IResult<&[u8], &[u8]> { + length_data(be_u32)(i) +} + +pub fn ssh_parse_key_exchange(i: &[u8]) -> IResult<&[u8], SshPacketKeyExchange> { + let (i, cookie) = take(16_usize)(i)?; + let (i, kex_algs) = parse_string(i)?; + let (i, server_host_key_algs) = parse_string(i)?; + let (i, encr_algs_client_to_server) = parse_string(i)?; + let (i, encr_algs_server_to_client) = parse_string(i)?; + let (i, mac_algs_client_to_server) = parse_string(i)?; + let (i, mac_algs_server_to_client) = parse_string(i)?; + let (i, comp_algs_client_to_server) = parse_string(i)?; + let (i, comp_algs_server_to_client) = parse_string(i)?; + let (i, langs_client_to_server) = parse_string(i)?; + let (i, langs_server_to_client) = parse_string(i)?; + let (i, first_kex_packet_follows) = be_u8(i)?; + let (i, reserved) = be_u32(i)?; + Ok(( + i, + SshPacketKeyExchange { + cookie, + kex_algs, + server_host_key_algs, + encr_algs_client_to_server, + encr_algs_server_to_client, + mac_algs_client_to_server, + mac_algs_server_to_client, + comp_algs_client_to_server, + comp_algs_server_to_client, + langs_client_to_server, + langs_server_to_client, + first_kex_packet_follows, + reserved, + }, + )) +} + +#[cfg(test)] +mod tests { + + use super::*; + use nom7::{Err, Needed}; + + /// Simple test of some valid data. + #[test] + fn test_ssh_parse_banner() { + let buf = b"SSH-Single-"; + let result = ssh_parse_banner(buf); + match result { + Ok((_, message)) => { + // Check the first message. + assert_eq!(message.protover, b"Single"); + assert_eq!(message.swver, b""); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf2 = b"SSH-2.0-Soft"; + let result2 = ssh_parse_banner(buf2); + match result2 { + Ok((_, message)) => { + // Check the first message. + assert_eq!(message.protover, b"2.0"); + assert_eq!(message.swver, b"Soft"); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + + #[test] + fn test_parse_line() { + let buf = b"SSH-Single\n"; + let result = ssh_parse_line(buf); + match result { + Ok((_, message)) => { + // Check the first message. + assert_eq!(message, b"SSH-Single"); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf2 = b"SSH-Double\r\n"; + let result2 = ssh_parse_line(buf2); + match result2 { + Ok((_, message)) => { + // Check the first message. + assert_eq!(message, b"SSH-Double"); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf3 = b"SSH-Oops\rMore\r\n"; + let result3 = ssh_parse_line(buf3); + match result3 { + Ok((rem, message)) => { + // Check the first message. + assert_eq!(message, b"SSH-Oops"); + assert_eq!(rem, b"More\r\n"); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf4 = b"SSH-Miss\r"; + let result4 = ssh_parse_line(buf4); + match result4 { + Ok((_, _)) => { + panic!("Expected incomplete result"); + } + Err(Err::Incomplete(_)) => { + //OK + assert_eq!(1, 1); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + let buf5 = b"\n"; + let result5 = ssh_parse_line(buf5); + match result5 { + Ok((_, message)) => { + // Check empty line + assert_eq!(message, b""); + } + Err(err) => { + panic!("Result should not be an error: {:?}.", err); + } + } + } + #[test] + fn test_parse_key_exchange() { + let client_key_exchange = [0x18 ,0x70 ,0xCB ,0xA4 ,0xA3 ,0xD4 ,0xDC ,0x88 ,0x6F + ,0xFD ,0x76 ,0x06 ,0xCF ,0x36 ,0x1B ,0xC6 ,0x00 ,0x00 ,0x01 ,0x0D ,0x63 ,0x75 ,0x72 ,0x76 + ,0x65 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 ,0x36 ,0x2C ,0x63 + ,0x75 ,0x72 ,0x76 ,0x65 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x40 ,0x6C ,0x69 ,0x62 ,0x73 ,0x73 ,0x68 ,0x2E ,0x6F ,0x72 ,0x67 ,0x2C ,0x65 ,0x63 + ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x65 ,0x63 ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 + ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2C ,0x65 ,0x63 ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 + ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 ,0x31 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 + ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 + ,0x2D ,0x65 ,0x78 ,0x63 ,0x68 ,0x61 ,0x6E ,0x67 ,0x65 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x36 ,0x2D ,0x73 ,0x68 ,0x61 ,0x35 ,0x31 + ,0x32 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x38 ,0x2D ,0x73 ,0x68 ,0x61 ,0x35 ,0x31 + ,0x32 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x34 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x34 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2C + ,0x65 ,0x78 ,0x74 ,0x2D ,0x69 ,0x6E ,0x66 ,0x6F ,0x2D ,0x63 ,0x00 ,0x00 ,0x01 ,0x66 ,0x65 + ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 + ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 + ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2D + ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 + ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 ,0x31 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 + ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F + ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 + ,0x73 ,0x74 ,0x70 ,0x32 ,0x35 ,0x36 ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2C ,0x65 ,0x63 ,0x64 + ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 + ,0x31 ,0x2C ,0x73 ,0x73 ,0x68 ,0x2D ,0x65 ,0x64 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x63 + ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 + ,0x31 ,0x32 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 + ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 + ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x73 ,0x73 + ,0x68 ,0x2D ,0x72 ,0x73 ,0x61 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x73 ,0x73 ,0x68 + ,0x2D ,0x65 ,0x64 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 + ,0x2D ,0x32 ,0x35 ,0x36 ,0x2C ,0x73 ,0x73 ,0x68 ,0x2D ,0x72 ,0x73 ,0x61 ,0x00 ,0x00 ,0x00 + ,0x6C ,0x63 ,0x68 ,0x61 ,0x63 ,0x68 ,0x61 ,0x32 ,0x30 ,0x2D ,0x70 ,0x6F ,0x6C ,0x79 ,0x31 + ,0x33 ,0x30 ,0x35 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D + ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 + ,0x31 ,0x39 ,0x32 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D + ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 + ,0x32 ,0x35 ,0x36 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x00 ,0x00 ,0x00 ,0x6C ,0x63 ,0x68 ,0x61 ,0x63 ,0x68 ,0x61 ,0x32 + ,0x30 ,0x2D ,0x70 ,0x6F ,0x6C ,0x79 ,0x31 ,0x33 ,0x30 ,0x35 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E + ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D + ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x39 ,0x32 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C + ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 + ,0x32 ,0x38 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x00 ,0x00 ,0x00 ,0xD5 + ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 + ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 + ,0x32 ,0x38 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 + ,0x35 ,0x36 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 + ,0x31 ,0x32 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2D ,0x65 + ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C + ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D + ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 + ,0x68 ,0x61 ,0x31 ,0x00 ,0x00 ,0x00 ,0xD5 ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x2D + ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D + ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 + ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 + ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 + ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 + ,0x36 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 + ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x00 ,0x00 ,0x00 ,0x1A ,0x6E + ,0x6F ,0x6E ,0x65 ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x00 ,0x00 ,0x00 ,0x1A ,0x6E + ,0x6F ,0x6E ,0x65 ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 + ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00]; + let cookie = [0x18, 0x70, 0xcb, 0xa4, 0xa3, 0xd4, 0xdc, 0x88, 0x6f, 0xfd, 0x76, 0x06, 0xcf, 0x36, 0x1b, 0xc6]; + let key_exchange = SshPacketKeyExchange { + cookie: &cookie, + kex_algs: b"curve25519-sha256,curve25519-sha256@libssh.org,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,ext-info-c", + server_host_key_algs: b"ecdsa-sha2-nistp256-cert-v01@openssh.com,ecdsa-sha2-nistp384-cert-v01@openssh.com,ecdsa-sha2-nistp521-cert-v01@openssh.com,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com,ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa", + encr_algs_client_to_server: b"chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com", + encr_algs_server_to_client: b"chacha20-poly1305@openssh.com,aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com", + mac_algs_client_to_server: b"umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", + mac_algs_server_to_client: b"umac-64-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1", + comp_algs_client_to_server: b"none,zlib@openssh.com,zlib", + comp_algs_server_to_client: b"none,zlib@openssh.com,zlib", + langs_client_to_server: b"", + langs_server_to_client: b"", + first_kex_packet_follows: 0, + reserved: 0, + }; + + let expected = Ok((b"" as &[u8], key_exchange)); + let res = ssh_parse_key_exchange(&client_key_exchange); + assert_eq!(res, expected); + } + + #[test] + fn test_parse_hassh() { + let client_key_exchange = [0x18 ,0x70 ,0xCB ,0xA4 ,0xA3 ,0xD4 ,0xDC ,0x88 ,0x6F + ,0xFD ,0x76 ,0x06 ,0xCF ,0x36 ,0x1B ,0xC6 ,0x00 ,0x00 ,0x01 ,0x0D ,0x63 ,0x75 ,0x72 ,0x76 + ,0x65 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 ,0x36 ,0x2C ,0x63 + ,0x75 ,0x72 ,0x76 ,0x65 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x40 ,0x6C ,0x69 ,0x62 ,0x73 ,0x73 ,0x68 ,0x2E ,0x6F ,0x72 ,0x67 ,0x2C ,0x65 ,0x63 + ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x65 ,0x63 ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 + ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2C ,0x65 ,0x63 ,0x64 ,0x68 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 + ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 ,0x31 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 + ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 + ,0x2D ,0x65 ,0x78 ,0x63 ,0x68 ,0x61 ,0x6E ,0x67 ,0x65 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x36 ,0x2D ,0x73 ,0x68 ,0x61 ,0x35 ,0x31 + ,0x32 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x38 ,0x2D ,0x73 ,0x68 ,0x61 ,0x35 ,0x31 + ,0x32 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x34 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x35 + ,0x36 ,0x2C ,0x64 ,0x69 ,0x66 ,0x66 ,0x69 ,0x65 ,0x2D ,0x68 ,0x65 ,0x6C ,0x6C ,0x6D ,0x61 + ,0x6E ,0x2D ,0x67 ,0x72 ,0x6F ,0x75 ,0x70 ,0x31 ,0x34 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2C + ,0x65 ,0x78 ,0x74 ,0x2D ,0x69 ,0x6E ,0x66 ,0x6F ,0x2D ,0x63 ,0x00 ,0x00 ,0x01 ,0x66 ,0x65 + ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 + ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 + ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2D + ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 + ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 ,0x31 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 + ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F + ,0x6D ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 + ,0x73 ,0x74 ,0x70 ,0x32 ,0x35 ,0x36 ,0x2C ,0x65 ,0x63 ,0x64 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x33 ,0x38 ,0x34 ,0x2C ,0x65 ,0x63 ,0x64 + ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x6E ,0x69 ,0x73 ,0x74 ,0x70 ,0x35 ,0x32 + ,0x31 ,0x2C ,0x73 ,0x73 ,0x68 ,0x2D ,0x65 ,0x64 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2D ,0x63 + ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 + ,0x31 ,0x32 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 ,0x6F ,0x70 ,0x65 + ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 + ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x73 ,0x73 + ,0x68 ,0x2D ,0x72 ,0x73 ,0x61 ,0x2D ,0x63 ,0x65 ,0x72 ,0x74 ,0x2D ,0x76 ,0x30 ,0x31 ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x73 ,0x73 ,0x68 + ,0x2D ,0x65 ,0x64 ,0x32 ,0x35 ,0x35 ,0x31 ,0x39 ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 + ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2C ,0x72 ,0x73 ,0x61 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 + ,0x2D ,0x32 ,0x35 ,0x36 ,0x2C ,0x73 ,0x73 ,0x68 ,0x2D ,0x72 ,0x73 ,0x61 ,0x00 ,0x00 ,0x00 + ,0x6C ,0x63 ,0x68 ,0x61 ,0x63 ,0x68 ,0x61 ,0x32 ,0x30 ,0x2D ,0x70 ,0x6F ,0x6C ,0x79 ,0x31 + ,0x33 ,0x30 ,0x35 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D + ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 + ,0x31 ,0x39 ,0x32 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D + ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 + ,0x32 ,0x35 ,0x36 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x00 ,0x00 ,0x00 ,0x6C ,0x63 ,0x68 ,0x61 ,0x63 ,0x68 ,0x61 ,0x32 + ,0x30 ,0x2D ,0x70 ,0x6F ,0x6C ,0x79 ,0x31 ,0x33 ,0x30 ,0x35 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E + ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x32 ,0x38 ,0x2D + ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 ,0x39 ,0x32 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C + ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D ,0x63 ,0x74 ,0x72 ,0x2C ,0x61 ,0x65 ,0x73 ,0x31 + ,0x32 ,0x38 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x61 ,0x65 ,0x73 ,0x32 ,0x35 ,0x36 ,0x2D ,0x67 ,0x63 ,0x6D ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x00 ,0x00 ,0x00 ,0xD5 + ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 + ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 + ,0x32 ,0x38 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 + ,0x35 ,0x36 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 + ,0x31 ,0x32 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E + ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2D ,0x65 + ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C + ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 + ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D + ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 + ,0x68 ,0x61 ,0x31 ,0x00 ,0x00 ,0x00 ,0xD5 ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x2D + ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D + ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 ,0x36 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F + ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 + ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x2D ,0x65 ,0x74 ,0x6D ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 + ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 ,0x63 ,0x2D ,0x36 ,0x34 ,0x40 + ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x75 ,0x6D ,0x61 + ,0x63 ,0x2D ,0x31 ,0x32 ,0x38 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 ,0x68 ,0x2E ,0x63 + ,0x6F ,0x6D ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x32 ,0x35 + ,0x36 ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x32 ,0x2D ,0x35 ,0x31 ,0x32 + ,0x2C ,0x68 ,0x6D ,0x61 ,0x63 ,0x2D ,0x73 ,0x68 ,0x61 ,0x31 ,0x00 ,0x00 ,0x00 ,0x1A ,0x6E + ,0x6F ,0x6E ,0x65 ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x00 ,0x00 ,0x00 ,0x1A ,0x6E + ,0x6F ,0x6E ,0x65 ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x40 ,0x6F ,0x70 ,0x65 ,0x6E ,0x73 ,0x73 + ,0x68 ,0x2E ,0x63 ,0x6F ,0x6D ,0x2C ,0x7A ,0x6C ,0x69 ,0x62 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 + ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00]; + let mut hassh_string: Vec<u8> = vec!(); + let mut hassh: Vec<u8> = vec!(); + match ssh_parse_key_exchange(&client_key_exchange){ + Ok((_, key_exchange)) => { + key_exchange.generate_hassh(&mut hassh_string, &mut hassh, &true); + } + Err(_) => { } + } + + assert_eq!(hassh_string, "curve25519-sha256,curve25519-sha256@libssh.org,\ + ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,\ + diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha256,\ + diffie-hellman-group14-sha1,ext-info-c;chacha20-poly1305@openssh.com,aes128-ctr,\ + aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com;umac-64-etm@openssh.com,\ + umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,\ + hmac-sha1-etm@openssh.com,umac-64@openssh.com,umac-128@openssh.com,\ + hmac-sha2-256,hmac-sha2-512,hmac-sha1;none,zlib@openssh.com,zlib".as_bytes().to_vec()); + + assert_eq!(hassh, "ec7378c1a92f5a8dde7e8b7a1ddf33d1".as_bytes().to_vec()); + } + + #[test] + fn test_parse_hassh_server() { + let server_key_exchange = [0x7d, 0x76, 0x4f, 0x78, 0x81, 0x9e, 0x10, 0xfa, 0x23, 0x72, + 0xb5, 0x15, 0x56, 0xba, 0xf9, 0x46, 0x00, 0x00, 0x01, 0x02, 0x63, 0x75, 0x72, 0x76, 0x65, 0x32, + 0x35, 0x35, 0x31, 0x39, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x2c, 0x63, 0x75, 0x72, 0x76, + 0x65, 0x32, 0x35, 0x35, 0x31, 0x39, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x40, 0x6c, 0x69, + 0x62, 0x73, 0x73, 0x68, 0x2e, 0x6f, 0x72, 0x67, 0x2c, 0x65, 0x63, 0x64, 0x68, 0x2d, 0x73, 0x68, + 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x2c, 0x65, 0x63, 0x64, 0x68, + 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x33, 0x38, 0x34, 0x2c, 0x65, + 0x63, 0x64, 0x68, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x35, 0x32, + 0x31, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, 0x6e, + 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2d, + 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, + 0x6c, 0x6c, 0x6d, 0x61, 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x36, 0x2d, 0x73, 0x68, + 0x61, 0x35, 0x31, 0x32, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, + 0x6d, 0x61, 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x38, 0x2d, 0x73, 0x68, 0x61, 0x35, + 0x31, 0x32, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, + 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x34, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, + 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, 0x6e, 0x2d, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x34, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0x41, + 0x73, 0x73, 0x68, 0x2d, 0x72, 0x73, 0x61, 0x2c, 0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, + 0x2d, 0x35, 0x31, 0x32, 0x2c, 0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, + 0x36, 0x2c, 0x65, 0x63, 0x64, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, + 0x74, 0x70, 0x32, 0x35, 0x36, 0x2c, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, + 0x39, 0x00, 0x00, 0x00, 0x6c, 0x63, 0x68, 0x61, 0x63, 0x68, 0x61, 0x32, 0x30, 0x2d, 0x70, 0x6f, + 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, + 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, + 0x73, 0x31, 0x39, 0x32, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, 0x36, 0x2d, + 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, + 0x36, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0x6c, 0x63, 0x68, 0x61, 0x63, 0x68, 0x61, 0x32, 0x30, 0x2d, 0x70, 0x6f, + 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, + 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, + 0x73, 0x31, 0x39, 0x32, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, 0x36, 0x2d, + 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, + 0x36, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0xd5, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x36, 0x34, 0x2d, 0x65, 0x74, 0x6d, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, + 0x63, 0x2d, 0x31, 0x32, 0x38, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, + 0x32, 0x35, 0x36, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, + 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, + 0x32, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x2d, 0x65, 0x74, 0x6d, 0x40, + 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, + 0x2d, 0x36, 0x34, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, + 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, 0x38, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, + 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, + 0x35, 0x36, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, + 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0xd5, 0x75, 0x6d, + 0x61, 0x63, 0x2d, 0x36, 0x34, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, 0x38, 0x2d, 0x65, + 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, + 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, 0x36, 0x2d, 0x65, 0x74, 0x6d, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, + 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, + 0x73, 0x68, 0x61, 0x31, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, + 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x36, 0x34, 0x40, 0x6f, 0x70, 0x65, + 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, + 0x38, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, + 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, 0x36, 0x2c, 0x68, 0x6d, 0x61, 0x63, + 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, + 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0x15, 0x6e, 0x6f, 0x6e, 0x65, 0x2c, 0x7a, 0x6c, 0x69, 0x62, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x15, + 0x6e, 0x6f, 0x6e, 0x65, 0x2c, 0x7a, 0x6c, 0x69, 0x62, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + let mut hassh_server_string: Vec<u8> = vec!(); + let mut hassh_server: Vec<u8> = vec!(); + match ssh_parse_key_exchange(&server_key_exchange){ + Ok((_, key_exchange)) => { + key_exchange.generate_hassh(&mut hassh_server_string, &mut hassh_server, &true); + } + Err(_) => { } + } + assert_eq!(hassh_server, "b12d2871a1189eff20364cf5333619ee".as_bytes().to_vec()); + } + + #[test] + fn test_parse_hassh_server_malicious() { + let server_key_exchange = [0x7d, 0x76, 0x4f, 0x78, 0x81, 0x9e, 0x10, 0xfa, 0x23, 0x72, + 0xb5, 0x15, 0x56, 0xba, 0xf9, 0x46, 0x00, 0x00, 0x01, 0x02, 0x75, 0x72, 0x76, 0x65, 0x32, + 0x35, 0x35, 0x31, 0x39, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x2c, 0x63, 0x75, 0x72, 0x76, + 0x65, 0x32, 0x35, 0x35, 0x31, 0x39, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x40, 0x6c, 0x69, + 0x62, 0x73, 0x73, 0x68, 0x2e, 0x6f, 0x72, 0x67, 0x2c, 0x65, 0x63, 0x64, 0x68, 0x2d, 0x73, 0x68, + 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x32, 0x35, 0x36, 0x2c, 0x65, 0x63, 0x64, 0x68, + 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x33, 0x38, 0x34, 0x2c, 0x65, + 0x63, 0x64, 0x68, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, 0x74, 0x70, 0x35, 0x32, + 0x31, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, 0x6e, + 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x2d, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2d, + 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, + 0x6c, 0x6c, 0x6d, 0x61, 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x36, 0x2d, 0x73, 0x68, + 0x61, 0x35, 0x31, 0x32, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, + 0x6d, 0x61, 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x38, 0x2d, 0x73, 0x68, 0x61, 0x35, + 0x31, 0x32, 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, + 0x6e, 0x2d, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x34, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, + 0x2c, 0x64, 0x69, 0x66, 0x66, 0x69, 0x65, 0x2d, 0x68, 0x65, 0x6c, 0x6c, 0x6d, 0x61, 0x6e, 0x2d, + 0x67, 0x72, 0x6f, 0x75, 0x70, 0x31, 0x34, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0x41, + 0x73, 0x73, 0x68, 0x2d, 0x72, 0x73, 0x61, 0x2c, 0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, + 0x2d, 0x35, 0x31, 0x32, 0x2c, 0x72, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, + 0x36, 0x2c, 0x65, 0x63, 0x64, 0x73, 0x61, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x6e, 0x69, 0x73, + 0x74, 0x70, 0x32, 0x35, 0x36, 0x2c, 0x73, 0x73, 0x68, 0x2d, 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, + 0x39, 0x00, 0x00, 0x00, 0x6c, 0x63, 0x68, 0x61, 0x63, 0x68, 0x61, 0x32, 0x30, 0x2d, 0x70, 0x6f, + 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, + 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, + 0x73, 0x31, 0x39, 0x32, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, 0x36, 0x2d, + 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, + 0x36, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0x6c, 0x63, 0x68, 0x61, 0x63, 0x68, 0x61, 0x32, 0x30, 0x2d, 0x70, 0x6f, + 0x6c, 0x79, 0x31, 0x33, 0x30, 0x35, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, + 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, + 0x73, 0x31, 0x39, 0x32, 0x2d, 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, 0x36, 0x2d, + 0x63, 0x74, 0x72, 0x2c, 0x61, 0x65, 0x73, 0x31, 0x32, 0x38, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x61, 0x65, 0x73, 0x32, 0x35, + 0x36, 0x2d, 0x67, 0x63, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x00, 0x00, 0x00, 0xd5, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x36, 0x34, 0x2d, 0x65, 0x74, 0x6d, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, + 0x63, 0x2d, 0x31, 0x32, 0x38, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, + 0x32, 0x35, 0x36, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, + 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, + 0x32, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, + 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x2d, 0x65, 0x74, 0x6d, 0x40, + 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, + 0x2d, 0x36, 0x34, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, + 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, 0x38, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, + 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, + 0x35, 0x36, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, + 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0xd5, 0x75, 0x6d, + 0x61, 0x63, 0x2d, 0x36, 0x34, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, 0x38, 0x2d, 0x65, + 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, + 0x6d, 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, 0x36, 0x2d, 0x65, 0x74, 0x6d, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, + 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, + 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, + 0x73, 0x68, 0x61, 0x31, 0x2d, 0x65, 0x74, 0x6d, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, + 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x36, 0x34, 0x40, 0x6f, 0x70, 0x65, + 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x75, 0x6d, 0x61, 0x63, 0x2d, 0x31, 0x32, + 0x38, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x2c, 0x68, 0x6d, + 0x61, 0x63, 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x32, 0x35, 0x36, 0x2c, 0x68, 0x6d, 0x61, 0x63, + 0x2d, 0x73, 0x68, 0x61, 0x32, 0x2d, 0x35, 0x31, 0x32, 0x2c, 0x68, 0x6d, 0x61, 0x63, 0x2d, 0x73, + 0x68, 0x61, 0x31, 0x00, 0x00, 0x00, 0x15, 0x6e, 0x6f, 0x6e, 0x65, 0x2c, 0x7a, 0x6c, 0x69, 0x62, + 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x15, + 0x6e, 0x6f, 0x6e, 0x65, 0x2c, 0x7a, 0x6c, 0x69, 0x62, 0x40, 0x6f, 0x70, 0x65, 0x6e, 0x73, 0x73, + 0x68, 0x2e, 0x63, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + + if let Err(e) = ssh_parse_key_exchange(&server_key_exchange) { + assert_eq!(e, Err::Incomplete(Needed::new(15964))); + } + else { + panic!("ssh_parse_key_exchange() parsed malicious key_exchange"); + } +} +} diff --git a/rust/src/ssh/ssh.rs b/rust/src/ssh/ssh.rs new file mode 100644 index 0000000..6280e0b --- /dev/null +++ b/rust/src/ssh/ssh.rs @@ -0,0 +1,509 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use super::parser; +use crate::applayer::*; +use crate::core::*; +use nom7::Err; +use std::ffi::CString; +use std::sync::atomic::{AtomicBool, Ordering}; + +static mut ALPROTO_SSH: AppProto = ALPROTO_UNKNOWN; +static HASSH_ENABLED: AtomicBool = AtomicBool::new(false); + +fn hassh_is_enabled() -> bool { + HASSH_ENABLED.load(Ordering::Relaxed) +} + +#[derive(AppLayerEvent)] +pub enum SSHEvent { + InvalidBanner, + LongBanner, + InvalidRecord, + LongKexRecord, +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialOrd, PartialEq, Eq)] +pub enum SSHConnectionState { + SshStateInProgress = 0, + SshStateBannerWaitEol = 1, + SshStateBannerDone = 2, + SshStateFinished = 3, +} + +const SSH_MAX_BANNER_LEN: usize = 256; +const SSH_RECORD_HEADER_LEN: usize = 6; +const SSH_MAX_REASSEMBLED_RECORD_LEN: usize = 65535; + +pub struct SshHeader { + record_left: u32, + record_left_msg: parser::MessageCode, + + flags: SSHConnectionState, + pub protover: Vec<u8>, + pub swver: Vec<u8>, + + pub hassh: Vec<u8>, + pub hassh_string: Vec<u8>, +} + +impl Default for SshHeader { + fn default() -> Self { + Self::new() + } +} + +impl SshHeader { + pub fn new() -> SshHeader { + Self { + record_left: 0, + record_left_msg: parser::MessageCode::Undefined(0), + + flags: SSHConnectionState::SshStateInProgress, + protover: Vec::new(), + swver: Vec::new(), + + hassh: Vec::new(), + hassh_string: Vec::new(), + } + } +} + +#[derive(Default)] +pub struct SSHTransaction { + pub srv_hdr: SshHeader, + pub cli_hdr: SshHeader, + + tx_data: AppLayerTxData, +} + +#[derive(Default)] +pub struct SSHState { + state_data: AppLayerStateData, + transaction: SSHTransaction, +} + +impl SSHState { + pub fn new() -> Self { + Default::default() + } + + fn set_event(&mut self, event: SSHEvent) { + self.transaction.tx_data.set_event(event as u8); + } + + fn parse_record( + &mut self, mut input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void, + ) -> AppLayerResult { + let (hdr, ohdr) = if !resp { + (&mut self.transaction.cli_hdr, &self.transaction.srv_hdr) + } else { + (&mut self.transaction.srv_hdr, &self.transaction.cli_hdr) + }; + let il = input.len(); + //first skip record left bytes + if hdr.record_left > 0 { + //should we check for overflow ? + let ilen = input.len() as u32; + if hdr.record_left > ilen { + hdr.record_left -= ilen; + return AppLayerResult::ok(); + } else { + let start = hdr.record_left as usize; + match hdr.record_left_msg { + // parse reassembled tcp segments + parser::MessageCode::Kexinit if hassh_is_enabled() => { + if let Ok((_rem, key_exchange)) = + parser::ssh_parse_key_exchange(&input[..start]) + { + key_exchange.generate_hassh( + &mut hdr.hassh_string, + &mut hdr.hassh, + &resp, + ); + } + hdr.record_left_msg = parser::MessageCode::Undefined(0); + } + _ => {} + } + input = &input[start..]; + hdr.record_left = 0; + } + } + //parse records out of input + while !input.is_empty() { + match parser::ssh_parse_record(input) { + Ok((rem, head)) => { + SCLogDebug!("SSH valid record {}", head); + match head.msg_code { + parser::MessageCode::Kexinit if hassh_is_enabled() => { + //let endkex = SSH_RECORD_HEADER_LEN + head.pkt_len - 2; + let endkex = input.len() - rem.len(); + if let Ok((_, key_exchange)) = parser::ssh_parse_key_exchange(&input[SSH_RECORD_HEADER_LEN..endkex]) { + key_exchange.generate_hassh(&mut hdr.hassh_string, &mut hdr.hassh, &resp); + } + } + parser::MessageCode::NewKeys => { + hdr.flags = SSHConnectionState::SshStateFinished; + if ohdr.flags >= SSHConnectionState::SshStateFinished { + unsafe { + AppLayerParserStateSetFlag( + pstate, + APP_LAYER_PARSER_NO_INSPECTION + | APP_LAYER_PARSER_NO_REASSEMBLY + | APP_LAYER_PARSER_BYPASS_READY, + ); + } + } + } + _ => {} + } + + input = rem; + //header and complete data (not returned) + } + Err(Err::Incomplete(_)) => { + match parser::ssh_parse_record_header(input) { + Ok((rem, head)) => { + SCLogDebug!("SSH valid record header {}", head); + let remlen = rem.len() as u32; + hdr.record_left = head.pkt_len - 2 - remlen; + //header with rem as incomplete data + match head.msg_code { + parser::MessageCode::NewKeys => { + hdr.flags = SSHConnectionState::SshStateFinished; + } + parser::MessageCode::Kexinit if hassh_is_enabled() => { + // check if buffer is bigger than maximum reassembled packet size + hdr.record_left = head.pkt_len - 2; + if hdr.record_left < SSH_MAX_REASSEMBLED_RECORD_LEN as u32 { + // saving type of incomplete kex message + hdr.record_left_msg = parser::MessageCode::Kexinit; + return AppLayerResult::incomplete( + (il - rem.len()) as u32, + head.pkt_len - 2 + ); + } + else { + SCLogDebug!("SSH buffer is bigger than maximum reassembled packet size"); + self.set_event(SSHEvent::LongKexRecord); + } + } + _ => {} + } + return AppLayerResult::ok(); + } + Err(Err::Incomplete(_)) => { + //we may have consumed data from previous records + if input.len() < SSH_RECORD_HEADER_LEN { + //do not trust nom incomplete value + return AppLayerResult::incomplete( + (il - input.len()) as u32, + SSH_RECORD_HEADER_LEN as u32, + ); + } else { + panic!("SSH invalid length record header"); + } + } + Err(_e) => { + SCLogDebug!("SSH invalid record header {}", _e); + self.set_event(SSHEvent::InvalidRecord); + return AppLayerResult::err(); + } + } + } + Err(_e) => { + SCLogDebug!("SSH invalid record {}", _e); + self.set_event(SSHEvent::InvalidRecord); + return AppLayerResult::err(); + } + } + } + return AppLayerResult::ok(); + } + + fn parse_banner( + &mut self, input: &[u8], resp: bool, pstate: *mut std::os::raw::c_void, + ) -> AppLayerResult { + let hdr = if !resp { + &mut self.transaction.cli_hdr + } else { + &mut self.transaction.srv_hdr + }; + if hdr.flags == SSHConnectionState::SshStateBannerWaitEol { + match parser::ssh_parse_line(input) { + Ok((rem, _)) => { + let mut r = self.parse_record(rem, resp, pstate); + if r.is_incomplete() { + //adds bytes consumed by banner to incomplete result + r.consumed += (input.len() - rem.len()) as u32; + } + return r; + } + Err(Err::Incomplete(_)) => { + return AppLayerResult::incomplete(0_u32, (input.len() + 1) as u32); + } + Err(_e) => { + SCLogDebug!("SSH invalid banner {}", _e); + self.set_event(SSHEvent::InvalidBanner); + return AppLayerResult::err(); + } + } + } + match parser::ssh_parse_line(input) { + Ok((rem, line)) => { + if let Ok((_, banner)) = parser::ssh_parse_banner(line) { + hdr.protover.extend(banner.protover); + if !banner.swver.is_empty() { + hdr.swver.extend(banner.swver); + } + hdr.flags = SSHConnectionState::SshStateBannerDone; + } else { + SCLogDebug!("SSH invalid banner"); + self.set_event(SSHEvent::InvalidBanner); + return AppLayerResult::err(); + } + if line.len() >= SSH_MAX_BANNER_LEN { + SCLogDebug!( + "SSH banner too long {} vs {}", + line.len(), + SSH_MAX_BANNER_LEN + ); + self.set_event(SSHEvent::LongBanner); + } + let mut r = self.parse_record(rem, resp, pstate); + if r.is_incomplete() { + //adds bytes consumed by banner to incomplete result + r.consumed += (input.len() - rem.len()) as u32; + } + return r; + } + Err(Err::Incomplete(_)) => { + if input.len() < SSH_MAX_BANNER_LEN { + //0 consumed, needs at least one more byte + return AppLayerResult::incomplete(0_u32, (input.len() + 1) as u32); + } else { + SCLogDebug!( + "SSH banner too long {} vs {} and waiting for eol", + input.len(), + SSH_MAX_BANNER_LEN + ); + if let Ok((_, banner)) = parser::ssh_parse_banner(input) { + hdr.protover.extend(banner.protover); + if !banner.swver.is_empty() { + hdr.swver.extend(banner.swver); + } + hdr.flags = SSHConnectionState::SshStateBannerWaitEol; + self.set_event(SSHEvent::LongBanner); + return AppLayerResult::ok(); + } else { + self.set_event(SSHEvent::InvalidBanner); + return AppLayerResult::err(); + } + } + } + Err(_e) => { + SCLogDebug!("SSH invalid banner {}", _e); + self.set_event(SSHEvent::InvalidBanner); + return AppLayerResult::err(); + } + } + } +} + +// C exports. + +export_tx_data_get!(rs_ssh_get_tx_data, SSHTransaction); +export_state_data_get!(rs_ssh_get_state_data, SSHState); + +#[no_mangle] +pub extern "C" fn rs_ssh_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = SSHState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut SSHState)); +} + +#[no_mangle] +pub extern "C" fn rs_ssh_state_tx_free(_state: *mut std::os::raw::c_void, _tx_id: u64) { + //do nothing +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_parse_request( + _flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = &mut cast_pointer!(state, SSHState); + let buf = stream_slice.as_slice(); + let hdr = &mut state.transaction.cli_hdr; + if hdr.flags < SSHConnectionState::SshStateBannerDone { + return state.parse_banner(buf, false, pstate); + } else { + return state.parse_record(buf, false, pstate); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_parse_response( + _flow: *const Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let state = &mut cast_pointer!(state, SSHState); + let buf = stream_slice.as_slice(); + let hdr = &mut state.transaction.srv_hdr; + if hdr.flags < SSHConnectionState::SshStateBannerDone { + return state.parse_banner(buf, true, pstate); + } else { + return state.parse_record(buf, true, pstate); + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_state_get_tx( + state: *mut std::os::raw::c_void, _tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, SSHState); + return &state.transaction as *const _ as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_ssh_state_get_tx_count(_state: *mut std::os::raw::c_void) -> u64 { + return 1; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_flags( + tx: *mut std::os::raw::c_void, direction: u8, +) -> SSHConnectionState { + let tx = cast_pointer!(tx, SSHTransaction); + if direction == Direction::ToServer.into() { + return tx.cli_hdr.flags; + } else { + return tx.srv_hdr.flags; + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, direction: u8, +) -> std::os::raw::c_int { + let tx = cast_pointer!(tx, SSHTransaction); + + if tx.cli_hdr.flags >= SSHConnectionState::SshStateFinished + && tx.srv_hdr.flags >= SSHConnectionState::SshStateFinished + { + return SSHConnectionState::SshStateFinished as i32; + } + + if direction == Direction::ToServer.into() { + if tx.cli_hdr.flags >= SSHConnectionState::SshStateBannerDone { + return SSHConnectionState::SshStateBannerDone as i32; + } + } else if tx.srv_hdr.flags >= SSHConnectionState::SshStateBannerDone { + return SSHConnectionState::SshStateBannerDone as i32; + } + + return SSHConnectionState::SshStateInProgress as i32; +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"ssh\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_register_parser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + //simple patterns, no probing + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 0, + state_new: rs_ssh_state_new, + state_free: rs_ssh_state_free, + tx_free: rs_ssh_state_tx_free, + parse_ts: rs_ssh_parse_request, + parse_tc: rs_ssh_parse_response, + get_tx_count: rs_ssh_state_get_tx_count, + get_tx: rs_ssh_state_get_tx, + tx_comp_st_ts: SSHConnectionState::SshStateFinished as i32, + tx_comp_st_tc: SSHConnectionState::SshStateFinished as i32, + tx_get_progress: rs_ssh_tx_get_alstate_progress, + get_eventinfo: Some(SSHEvent::get_event_info), + get_eventinfo_byid: Some(SSHEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: None, + get_tx_data: rs_ssh_get_tx_data, + get_state_data: rs_ssh_get_state_data, + apply_tx_config: None, + flags: 0, + truncate: None, + get_frame_id_by_name: None, + get_frame_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_SSH = alproto; + if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust ssh parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for SSH."); + } +} + +#[no_mangle] +pub extern "C" fn rs_ssh_enable_hassh() { + HASSH_ENABLED.store(true, Ordering::Relaxed) +} + +#[no_mangle] +pub extern "C" fn rs_ssh_hassh_is_enabled() -> bool { + hassh_is_enabled() +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ssh_tx_get_log_condition( tx: *mut std::os::raw::c_void) -> bool { + let tx = cast_pointer!(tx, SSHTransaction); + + if rs_ssh_hassh_is_enabled() { + if tx.cli_hdr.flags == SSHConnectionState::SshStateFinished && + tx.srv_hdr.flags == SSHConnectionState::SshStateFinished { + return true; + } + } + else if tx.cli_hdr.flags == SSHConnectionState::SshStateBannerDone && + tx.srv_hdr.flags == SSHConnectionState::SshStateBannerDone { + return true; + } + return false; +} diff --git a/rust/src/telnet/mod.rs b/rust/src/telnet/mod.rs new file mode 100644 index 0000000..38685c7 --- /dev/null +++ b/rust/src/telnet/mod.rs @@ -0,0 +1,21 @@ +/* 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. + */ + +//! Telnet application layer and parser module. + +pub mod telnet; +mod parser; diff --git a/rust/src/telnet/parser.rs b/rust/src/telnet/parser.rs new file mode 100644 index 0000000..e2dbfa4 --- /dev/null +++ b/rust/src/telnet/parser.rs @@ -0,0 +1,77 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::common::nom7::take_until_and_consume; +use nom7::combinator::peek; +use nom7::bytes::complete::take; +use nom7::{IResult}; +use nom7::number::streaming::le_u8; +use nom7::bytes::streaming::tag; +use nom7::bytes::streaming::{take_until}; + +pub fn peek_message_is_ctl(i: &[u8]) -> IResult<&[u8], bool> { + let (i, v) = peek(le_u8)(i)?; + Ok((i, v == b'\xff')) +} + +pub enum TelnetMessageType<'a> { + Control(&'a [u8]), + Data(&'a [u8]), +} + +pub fn parse_ctl_suboption<'a>(i: &'a[u8], full: &'a[u8]) -> IResult<&'a[u8], &'a[u8]> { + let (i, _sc) = le_u8(i)?; + let tag: &[u8] = b"\xff\xf0"; + let (i, x) = take_until(tag)(i)?; + let o = &full[..(x.len()+3)]; + Ok((i, o)) +} + +pub fn parse_ctl_message(oi: &[u8]) -> IResult<&[u8], &[u8]> { + let (i, _) = tag(b"\xff")(oi)?; + let (i, cmd) = le_u8(i)?; + let (i, d) = match cmd { + 251..=254 => take(3_usize)(oi)?, + 240..=249 => take(2_usize)(oi)?, + 250 => parse_ctl_suboption(i, oi)?, + _ => take(2_usize)(oi)?, // TODO maybe an error or some other special handling + }; + Ok((i, d)) +} + +pub fn parse_message(i: &[u8]) -> IResult<&[u8], TelnetMessageType> { + let (i, v) = peek(le_u8)(i)?; + if v == b'\xff' { + let (i, c) = parse_ctl_message(i)?; + Ok((i, TelnetMessageType::Control(c))) + } else { + let (i, t) = take_until_and_consume(b"\n")(i)?; + Ok((i, TelnetMessageType::Data(t))) + } +} + +// 'login: ', 'Password: ', possibly with leading ctls +pub fn parse_welcome_message(i: &[u8]) -> IResult<&[u8], TelnetMessageType> { + let (i, v) = peek(le_u8)(i)?; + if v == b'\xff' { + let (i, c) = parse_ctl_message(i)?; + Ok((i, TelnetMessageType::Control(c))) + } else { + let (i, t) = take_until_and_consume(b": ")(i)?; + Ok((i, TelnetMessageType::Data(t))) + } +} diff --git a/rust/src/telnet/telnet.rs b/rust/src/telnet/telnet.rs new file mode 100644 index 0000000..f1e7eec --- /dev/null +++ b/rust/src/telnet/telnet.rs @@ -0,0 +1,563 @@ +/* Copyright (C) 2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std; +use crate::core::{ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_TCP}; +use crate::applayer::{self, *}; +use crate::frames::*; +use std::ffi::CString; +use nom7::IResult; +use super::parser; + +static mut ALPROTO_TELNET: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent)] +enum TelnetEvent {} + +#[derive(AppLayerFrameType)] +pub enum TelnetFrameType { + Pdu, + Ctl, + Data, +} + +#[derive(Default)] +pub struct TelnetTransaction { + tx_id: u64, + tx_data: AppLayerTxData, +} + +impl Transaction for TelnetTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +pub enum TelnetProtocolState { + Idle, + LoginSent, + LoginRecv, + PasswdSent, + PasswdRecv, + AuthOk, + AuthFail, +} + +pub struct TelnetState { + state_data: AppLayerStateData, + tx_id: u64, + transactions: Vec<TelnetTransaction>, + request_gap: bool, + response_gap: bool, + + request_frame: Option<Frame>, + response_frame: Option<Frame>, + + /// either control or data frame + request_specific_frame: Option<Frame>, + /// either control or data frame + response_specific_frame: Option<Frame>, + state: TelnetProtocolState, +} + +impl State<TelnetTransaction> for TelnetState { + fn get_transaction_count(&self) -> usize { + self.transactions.len() + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&TelnetTransaction> { + self.transactions.get(index) + } +} + +impl Default for TelnetState { + fn default() -> Self { + Self::new() + } +} + +impl TelnetState { + pub fn new() -> Self { + Self { + state_data: AppLayerStateData::new(), + tx_id: 0, + transactions: Vec::new(), + request_gap: false, + response_gap: false, + request_frame: None, + request_specific_frame: None, + response_frame: None, + response_specific_frame: None, + state: TelnetProtocolState::Idle, + } + } + + // Free a transaction by ID. + 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.tx_id == tx_id + 1 { + found = true; + index = i; + break; + } + } + if found { + self.transactions.remove(index); + } + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&TelnetTransaction> { + self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) + } + + fn _find_request(&mut self) -> Option<&mut TelnetTransaction> { + // TODO + None + } + + // app-layer-frame-documentation tag start: parse_request + fn parse_request( + &mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8], + ) -> AppLayerResult { + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + // If there was gap, check we can sync up again. + if self.request_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this telnet, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.request_gap = false; + } + + let mut start = input; + while !start.is_empty() { + if self.request_frame.is_none() { + self.request_frame = Frame::new( + flow, + stream_slice, + start, + -1_i64, + TelnetFrameType::Pdu as u8, + ); + } + if self.request_specific_frame.is_none() { + if let Ok((_, is_ctl)) = parser::peek_message_is_ctl(start) { + let f = if is_ctl { + Frame::new( + flow, + stream_slice, + start, + -1_i64, + TelnetFrameType::Ctl as u8, + ) + } else { + Frame::new( + flow, + stream_slice, + start, + -1_i64, + TelnetFrameType::Data as u8, + ) + // app-layer-frame-documentation tag end: parse_request + }; + self.request_specific_frame = f; + } + } + // app-layer-frame-documentation tag start: update frame_len + match parser::parse_message(start) { + Ok((rem, request)) => { + let consumed = start.len() - rem.len(); + if rem.len() == start.len() { + panic!("lockup"); + } + start = rem; + + if let Some(frame) = &self.request_frame { + frame.set_len(flow, consumed as i64); + // app-layer-frame-documentation tag end: update frame_len + self.request_frame = None; + } + if let Some(frame) = &self.request_specific_frame { + frame.set_len(flow, consumed as i64); + self.request_specific_frame = None; + } + + if let parser::TelnetMessageType::Data(d) = request { + match self.state { + TelnetProtocolState::LoginSent => { + self.state = TelnetProtocolState::LoginRecv; + } + TelnetProtocolState::PasswdSent => { + self.state = TelnetProtocolState::PasswdRecv; + } + TelnetProtocolState::AuthOk => { + let _message = std::str::from_utf8(d); + if let Ok(_message) = _message { + SCLogDebug!("=> {}", _message); + } + } + _ => {} + } + } else if let parser::TelnetMessageType::Control(_c) = request { + SCLogDebug!("request {:?}", _c); + } + } + Err(nom7::Err::Incomplete(_)) => { + // Not enough data. This parser doesn't give us a good indication + // of how much data is missing so just ask for one more byte so the + // parse is called as soon as more data is received. + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_) => { + return AppLayerResult::err(); + } + } + } + + // Input was fully consumed. + return AppLayerResult::ok(); + } + + fn parse_response(&mut self, flow: *const Flow, stream_slice: &StreamSlice, input: &[u8]) -> AppLayerResult { + // We're not interested in empty responses. + if input.is_empty() { + return AppLayerResult::ok(); + } + + if self.response_gap { + if probe(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this telnet, we'll just try again next time. + return AppLayerResult::ok(); + } + + // It looks like we're in sync with a message header, clear gap + // state and keep parsing. + self.response_gap = false; + } + let mut start = input; + while !start.is_empty() { + if self.response_frame.is_none() { + self.response_frame = Frame::new(flow, stream_slice, start, -1_i64, TelnetFrameType::Pdu as u8); + } + if self.response_specific_frame.is_none() { + if let Ok((_, is_ctl)) = parser::peek_message_is_ctl(start) { + self.response_specific_frame = if is_ctl { + Frame::new(flow, stream_slice, start, -1_i64, TelnetFrameType::Ctl as u8) + } else { + Frame::new(flow, stream_slice, start, -1_i64, TelnetFrameType::Data as u8) + }; + } + } + + let r = match self.state { + TelnetProtocolState::Idle => parser::parse_welcome_message(start), + TelnetProtocolState::AuthFail => parser::parse_welcome_message(start), + TelnetProtocolState::LoginRecv => parser::parse_welcome_message(start), + _ => parser::parse_message(start), + }; + match r { + Ok((rem, response)) => { + let consumed = start.len() - rem.len(); + start = rem; + + if let Some(frame) = &self.response_frame { + frame.set_len(flow, consumed as i64); + self.response_frame = None; + } + if let Some(frame) = &self.response_specific_frame { + frame.set_len(flow, consumed as i64); + self.response_specific_frame = None; + } + + if let parser::TelnetMessageType::Data(d) = response { + match self.state { + TelnetProtocolState::Idle | + TelnetProtocolState::AuthFail => { + self.state = TelnetProtocolState::LoginSent; + }, + TelnetProtocolState::LoginRecv => { + self.state = TelnetProtocolState::PasswdSent; + }, + TelnetProtocolState::PasswdRecv => { + if let Ok(message) = std::str::from_utf8(d) { + match message { + "Login incorrect" => { + SCLogDebug!("LOGIN FAILED"); + self.state = TelnetProtocolState::AuthFail; + }, + "" => { + + }, + &_ => { + SCLogDebug!("LOGIN OK"); + self.state = TelnetProtocolState::AuthOk; + }, + } + } + }, + TelnetProtocolState::AuthOk => { + let _message = std::str::from_utf8(d); + if let Ok(_message) = _message { + SCLogDebug!("<= {}", _message); + } + }, + _ => {}, + } + } else if let parser::TelnetMessageType::Control(_c) = response { + SCLogDebug!("response {:?}", _c); + } + } + Err(nom7::Err::Incomplete(_)) => { + let consumed = input.len() - start.len(); + let needed = start.len() + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_e) => { + SCLogDebug!("error! {}", _e); + return AppLayerResult::err(); + } + } + } + + // All input was fully consumed. + return AppLayerResult::ok(); + } + + fn on_request_gap(&mut self, _size: u32) { + self.request_gap = true; + } + + fn on_response_gap(&mut self, _size: u32) { + self.response_gap = true; + } +} + +/// Probe for a valid header. +/// +fn probe(input: &[u8]) -> IResult<&[u8], ()> { + // TODO see if we can implement something here. Ctl message is easy, + // and 'login: ' is common, but we can have random text and possibly + // other output as well. So for now data on port 23 is it. + Ok((input, ())) +} + +// C exports. + +/// C entry point for a probing parser. +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_probing_parser( + _flow: *const Flow, + _direction: u8, + input: *const u8, + input_len: u32, + _rdir: *mut u8 +) -> AppProto { + // Need at least 2 bytes. + if input_len > 1 && !input.is_null() { + let slice = build_slice!(input, input_len as usize); + if probe(slice).is_ok() { + SCLogDebug!("telnet detected"); + return ALPROTO_TELNET; + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_telnet_state_new(_orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto) -> *mut std::os::raw::c_void { + let state = TelnetState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut std::os::raw::c_void; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(Box::from_raw(state as *mut TelnetState)); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_tx_free( + state: *mut std::os::raw::c_void, + tx_id: u64, +) { + let state = cast_pointer!(state, TelnetState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_parse_request( + flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0; + + if eof { + // If needed, handle EOF, or pass it into the parser. + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, TelnetState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_request_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_request(flow, &stream_slice, buf) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_parse_response( + flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + stream_slice: StreamSlice, + _data: *const std::os::raw::c_void +) -> AppLayerResult { + let _eof = AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; + let state = cast_pointer!(state, TelnetState); + + if stream_slice.is_gap() { + // Here we have a gap signaled by the input being null, but a greater + // than 0 input_len which provides the size of the gap. + state.on_response_gap(stream_slice.gap_size()); + AppLayerResult::ok() + } else { + let buf = stream_slice.as_slice(); + state.parse_response(flow, &stream_slice, buf) + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_get_tx( + state: *mut std::os::raw::c_void, + tx_id: u64, +) -> *mut std::os::raw::c_void { + let state = cast_pointer!(state, TelnetState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_state_get_tx_count( + state: *mut std::os::raw::c_void, +) -> u64 { + let state = cast_pointer!(state, TelnetState); + return state.tx_id; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_tx_get_alstate_progress( + tx: *mut std::os::raw::c_void, + _direction: u8, +) -> std::os::raw::c_int { + let _tx = cast_pointer!(tx, TelnetTransaction); + // TODO + return 0; +} + +export_tx_data_get!(rs_telnet_get_tx_data, TelnetTransaction); +export_state_data_get!(rs_telnet_get_state_data, TelnetState); + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"telnet\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_telnet_register_parser() { + let default_port = CString::new("[23]").unwrap(); + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char, + default_port: default_port.as_ptr(), + ipproto: IPPROTO_TCP, + probe_ts: Some(rs_telnet_probing_parser), + probe_tc: Some(rs_telnet_probing_parser), + min_depth: 0, + max_depth: 16, + state_new: rs_telnet_state_new, + state_free: rs_telnet_state_free, + tx_free: rs_telnet_state_tx_free, + parse_ts: rs_telnet_parse_request, + parse_tc: rs_telnet_parse_response, + get_tx_count: rs_telnet_state_get_tx_count, + get_tx: rs_telnet_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_telnet_tx_get_alstate_progress, + get_eventinfo: Some(TelnetEvent::get_event_info), + get_eventinfo_byid : Some(TelnetEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(applayer::state_get_tx_iterator::<TelnetState, TelnetTransaction>), + get_tx_data: rs_telnet_get_tx_data, + get_state_data: rs_telnet_get_state_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: None, + get_frame_id_by_name: Some(TelnetFrameType::ffi_id_from_name), + get_frame_name_by_id: Some(TelnetFrameType::ffi_name_from_id), + + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if AppLayerProtoDetectConfProtoDetectionEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_TELNET = alproto; + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogDebug!("Rust telnet parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for TELNET."); + } +} diff --git a/rust/src/tftp/log.rs b/rust/src/tftp/log.rs new file mode 100644 index 0000000..b483703 --- /dev/null +++ b/rust/src/tftp/log.rs @@ -0,0 +1,43 @@ +/* Copyright (C) 2017-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Clément Galland <clement.galland@epita.fr> + +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::tftp::tftp::TFTPTransaction; + +fn tftp_log_request(tx: &mut TFTPTransaction, + jb: &mut JsonBuilder) + -> Result<(), JsonError> +{ + match tx.opcode { + 1 => jb.set_string("packet", "read")?, + 2 => jb.set_string("packet", "write")?, + _ => jb.set_string("packet", "error")? + }; + jb.set_string("file", tx.filename.as_str())?; + jb.set_string("mode", tx.mode.as_str())?; + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_tftp_log_json_request(tx: &mut TFTPTransaction, + jb: &mut JsonBuilder) + -> bool +{ + tftp_log_request(tx, jb).is_ok() +} diff --git a/rust/src/tftp/mod.rs b/rust/src/tftp/mod.rs new file mode 100644 index 0000000..6ae29ac --- /dev/null +++ b/rust/src/tftp/mod.rs @@ -0,0 +1,23 @@ +/* Copyright (C) 2017 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! TFTP parser, logger and application layer module. + +// written by Clément Galland <clement.galland@epita.fr> + +pub mod tftp; +pub mod log; diff --git a/rust/src/tftp/tftp.rs b/rust/src/tftp/tftp.rs new file mode 100644 index 0000000..1b093fe --- /dev/null +++ b/rust/src/tftp/tftp.rs @@ -0,0 +1,280 @@ +/* Copyright (C) 2017-2021 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by Clément Galland <clement.galland@epita.fr> + +use std::str; +use std; +use nom7::IResult; +use nom7::combinator::map_res; +use nom7::bytes::streaming::{tag, take_while}; +use nom7::number::streaming::be_u8; + +use crate::applayer::{AppLayerTxData,AppLayerStateData}; + +const READREQUEST: u8 = 1; +const WRITEREQUEST: u8 = 2; +const DATA: u8 = 3; +const ACK: u8 = 4; +const ERROR: u8 = 5; + +#[derive(Debug, PartialEq, Eq)] +pub struct TFTPTransaction { + pub opcode : u8, + pub filename : String, + pub mode : String, + id: u64, + tx_data: AppLayerTxData, +} + +pub struct TFTPState { + state_data: AppLayerStateData, + pub transactions : Vec<TFTPTransaction>, + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, +} + +impl TFTPState { + fn get_tx_by_id(&mut self, tx_id: u64) -> Option<&TFTPTransaction> { + self.transactions.iter().find(|&tx| tx.id == tx_id + 1) + } + + fn free_tx(&mut self, tx_id: u64) { + let tx = self.transactions.iter().position(|tx| tx.id == tx_id + 1); + debug_assert!(tx.is_some()); + if let Some(idx) = tx { + let _ = self.transactions.remove(idx); + } + } +} + +impl TFTPTransaction { + pub fn new(opcode : u8, filename : String, mode : String) -> TFTPTransaction { + TFTPTransaction { + opcode, + filename, + mode : mode.to_lowercase(), + id : 0, + tx_data: AppLayerTxData::new(), + } + } + pub fn is_mode_ok(&self) -> bool { + match self.mode.as_str() { + "netascii" | "mail" | "octet" => true, + _ => false + } + } + pub fn is_opcode_ok(&self) -> bool { + match self.opcode { + READREQUEST | WRITEREQUEST | ACK | DATA | ERROR => true, + _ => false + } + } +} + +#[no_mangle] +pub extern "C" fn rs_tftp_state_alloc() -> *mut std::os::raw::c_void { + let state = TFTPState { state_data: AppLayerStateData::new(), transactions : Vec::new(), tx_id: 0, }; + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut _; +} + +#[no_mangle] +pub extern "C" fn rs_tftp_state_free(state: *mut std::os::raw::c_void) { + std::mem::drop(unsafe { Box::from_raw(state as *mut TFTPState) }); +} + +#[no_mangle] +pub extern "C" fn rs_tftp_state_tx_free(state: &mut TFTPState, + tx_id: u64) { + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_tftp_get_tx(state: &mut TFTPState, + tx_id: u64) -> *mut std::os::raw::c_void { + match state.get_tx_by_id(tx_id) { + Some(tx) => tx as *const _ as *mut _, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_tftp_get_tx_cnt(state: &mut TFTPState) -> u64 { + return state.tx_id; +} + +fn getstr(i: &[u8]) -> IResult<&[u8], &str> { + map_res( + take_while(|c| c != 0), + str::from_utf8 + )(i) +} + +fn tftp_request(slice: &[u8]) -> IResult<&[u8], TFTPTransaction> { + let (i, _) = tag([0])(slice)?; + let (i, opcode) = be_u8(i)?; + let (i, filename) = getstr(i)?; + let (i, _) = tag([0])(i)?; + let (i, mode) = getstr(i)?; + Ok((i, + TFTPTransaction::new(opcode, String::from(filename), String::from(mode)) + ) + ) +} + +fn parse_tftp_request(input: &[u8]) -> Option<TFTPTransaction> { + match tftp_request(input) { + Ok((_, tx)) => { + if !tx.is_mode_ok() { + return None; + } + if !tx.is_opcode_ok() { + return None; + } + return Some(tx); + } + Err(_) => { + return None; + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_tftp_request(state: &mut TFTPState, + input: *const u8, + len: u32) -> i64 { + let buf = std::slice::from_raw_parts(input, len as usize); + match parse_tftp_request(buf) { + Some(mut tx) => { + state.tx_id += 1; + tx.id = state.tx_id; + state.transactions.push(tx); + 0 + }, + None => { + -1 + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_tftp_get_tx_data( + tx: *mut std::os::raw::c_void) + -> *mut AppLayerTxData +{ + let tx = cast_pointer!(tx, TFTPTransaction); + return &mut tx.tx_data; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_tftp_get_state_data( + state: *mut std::os::raw::c_void) + -> *mut AppLayerStateData +{ + let state = cast_pointer!(state, TFTPState); + return &mut state.state_data; +} + +#[cfg(test)] +mod test { + use super::*; + static READ_REQUEST: [u8; 20] = [ + 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, + ]; + /* filename not terminated */ + static READ_REQUEST_INVALID_1: [u8; 20] = [ + 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x6e, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, + ]; + /* garbage */ + static READ_REQUEST_INVALID_2: [u8; 3] = [ + 0xff, 0xff, 0xff, + ]; + static WRITE_REQUEST: [u8; 20] = [ + 0x00, 0x02, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, + ]; + /* filename not terminated */ + static INVALID_OPCODE: [u8; 20] = [ + 0x00, 0x06, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x6e, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x00, + ]; + static INVALID_MODE: [u8; 20] = [ + 0x00, 0x01, 0x72, 0x66, 0x63, 0x31, 0x33, 0x35, 0x30, 0x2e, 0x74, 0x78, 0x74, 0x00, 0x63, 0x63, 0x63, 0x63, 0x63, 0x00, + ]; + + #[test] + pub fn test_parse_tftp_read_request_1() { + let tx = TFTPTransaction { + opcode: READREQUEST, + filename: String::from("rfc1350.txt"), + mode: String::from("octet"), + id: 0, + tx_data: AppLayerTxData::new(), + }; + + match parse_tftp_request(&READ_REQUEST[..]) { + Some(txp) => { + assert_eq!(tx, txp); + } + None => { + assert!(true); + } + } + } + + #[test] + pub fn test_parse_tftp_write_request_1() { + let tx = TFTPTransaction { + opcode: WRITEREQUEST, + filename: String::from("rfc1350.txt"), + mode: String::from("octet"), + id: 0, + tx_data: AppLayerTxData::new(), + }; + + match parse_tftp_request(&WRITE_REQUEST[..]) { + Some(txp) => { + assert_eq!(tx, txp); + } + None => { + assert!(true, "fadfasd"); + } + } + } + + // Invalid request: filename not terminated + #[test] + pub fn test_parse_tftp_read_request_2() { + assert_eq!(None, parse_tftp_request(&READ_REQUEST_INVALID_1[..])); + } + + // Invalid request: garbage input + #[test] + pub fn test_parse_tftp_read_request_3() { + assert_eq!(None, parse_tftp_request(&READ_REQUEST_INVALID_2[..])); + } + + #[test] + pub fn test_parse_tftp_invalid_opcode_1() { + assert_eq!(None, parse_tftp_request(&INVALID_OPCODE[..])); + } + + #[test] + pub fn test_parse_tftp_invalid_mode() { + + assert_eq!(None, parse_tftp_request(&INVALID_MODE[..])); + } +} diff --git a/rust/src/util.rs b/rust/src/util.rs new file mode 100644 index 0000000..d710946 --- /dev/null +++ b/rust/src/util.rs @@ -0,0 +1,26 @@ +/* Copyright (C) 2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Utility module. + +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn rs_check_utf8(val: *const c_char) -> bool { + CStr::from_ptr(val).to_str().is_ok() +} diff --git a/rust/src/x509/log.rs b/rust/src/x509/log.rs new file mode 100644 index 0000000..adb6464 --- /dev/null +++ b/rust/src/x509/log.rs @@ -0,0 +1,40 @@ +/* Copyright (C) 2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use crate::jsonbuilder::JsonBuilder; +use crate::x509::time::format_timestamp; +use std::ffi::CStr; +use std::os::raw::c_char; + +/// Helper function to log a TLS timestamp from C to JSON with the +/// provided key. The format of the timestamp is ISO 8601 timestamp +/// with no sub-second or offset information as UTC is assumed. +/// +/// # Safety +/// +/// FFI function that dereferences pointers from C. +#[no_mangle] +pub unsafe extern "C" fn sc_x509_log_timestamp( + jb: &mut JsonBuilder, key: *const c_char, timestamp: i64, +) -> bool { + if let Ok(key) = CStr::from_ptr(key).to_str() { + if let Ok(timestamp) = format_timestamp(timestamp) { + return jb.set_string(key, ×tamp).is_ok(); + } + } + false +} diff --git a/rust/src/x509/mod.rs b/rust/src/x509/mod.rs new file mode 100644 index 0000000..c87928c --- /dev/null +++ b/rust/src/x509/mod.rs @@ -0,0 +1,153 @@ +/* Copyright (C) 2019-2020 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +//! Module for SSL/TLS X.509 certificates parser and decoder. + +// written by Pierre Chifflier <chifflier@wzdftpd.net> + +use crate::common::rust_string_to_c; +use nom7::Err; +use std; +use std::os::raw::c_char; +use x509_parser::prelude::*; +mod time; +mod log; + +#[repr(u32)] +pub enum X509DecodeError { + _Success = 0, + /// Generic decoding error + InvalidCert, + /// Some length does not match, or certificate is incomplete + InvalidLength, + InvalidVersion, + InvalidSerial, + InvalidAlgorithmIdentifier, + InvalidX509Name, + InvalidDate, + InvalidExtensions, + /// DER structure is invalid + InvalidDER, +} + +pub struct X509(X509Certificate<'static>); + +/// Attempt to parse a X.509 from input, and return a pointer to the parsed object if successful. +/// +/// # Safety +/// +/// input must be a valid buffer of at least input_len bytes +#[no_mangle] +pub unsafe extern "C" fn rs_x509_decode( + input: *const u8, + input_len: u32, + err_code: *mut u32, +) -> *mut X509 { + let slice = std::slice::from_raw_parts(input, input_len as usize); + let res = X509Certificate::from_der(slice); + match res { + Ok((_rem, cert)) => Box::into_raw(Box::new(X509(cert))), + Err(e) => { + let error = x509_parse_error_to_errcode(&e); + *err_code = error as u32; + std::ptr::null_mut() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_subject(ptr: *const X509) -> *mut c_char { + if ptr.is_null() { + return std::ptr::null_mut(); + } + let x509 = cast_pointer! {ptr, X509}; + let subject = x509.0.tbs_certificate.subject.to_string(); + rust_string_to_c(subject) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_issuer(ptr: *const X509) -> *mut c_char { + if ptr.is_null() { + return std::ptr::null_mut(); + } + let x509 = cast_pointer! {ptr, X509}; + let issuer = x509.0.tbs_certificate.issuer.to_string(); + rust_string_to_c(issuer) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_serial(ptr: *const X509) -> *mut c_char { + if ptr.is_null() { + return std::ptr::null_mut(); + } + let x509 = cast_pointer! {ptr, X509}; + let raw_serial = x509.0.tbs_certificate.raw_serial(); + let v: Vec<_> = raw_serial.iter().map(|x| format!("{:02X}", x)).collect(); + let serial = v.join(":"); + rust_string_to_c(serial) +} + +/// Extract validity from input X.509 object +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_x509_decode` +#[no_mangle] +pub unsafe extern "C" fn rs_x509_get_validity( + ptr: *const X509, + not_before: *mut i64, + not_after: *mut i64, +) -> i32 { + if ptr.is_null() { + return -1; + } + let x509 = &*ptr; + let n_b = x509.0.validity().not_before.timestamp(); + let n_a = x509.0.validity().not_after.timestamp(); + *not_before = n_b; + *not_after = n_a; + 0 +} + +/// Free a X.509 object allocated by Rust +/// +/// # Safety +/// +/// ptr must be a valid object obtained using `rs_x509_decode` +#[no_mangle] +pub unsafe extern "C" fn rs_x509_free(ptr: *mut X509) { + if ptr.is_null() { + return; + } + drop(Box::from_raw(ptr)); +} + +fn x509_parse_error_to_errcode(e: &Err<X509Error>) -> X509DecodeError { + match e { + Err::Incomplete(_) => X509DecodeError::InvalidLength, + Err::Error(e) | Err::Failure(e) => match e { + X509Error::InvalidVersion => X509DecodeError::InvalidVersion, + X509Error::InvalidSerial => X509DecodeError::InvalidSerial, + X509Error::InvalidAlgorithmIdentifier => X509DecodeError::InvalidAlgorithmIdentifier, + X509Error::InvalidX509Name => X509DecodeError::InvalidX509Name, + X509Error::InvalidDate => X509DecodeError::InvalidDate, + X509Error::InvalidExtensions => X509DecodeError::InvalidExtensions, + X509Error::Der(_) => X509DecodeError::InvalidDER, + _ => X509DecodeError::InvalidCert, + }, + } +} diff --git a/rust/src/x509/time.rs b/rust/src/x509/time.rs new file mode 100644 index 0000000..507b39c --- /dev/null +++ b/rust/src/x509/time.rs @@ -0,0 +1,59 @@ +/* Copyright (C) 2023 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +use std::os::raw::c_char; +use time::macros::format_description; + +/// Format a timestamp in an ISO format suitable for TLS logging. +/// +/// Negative timestamp values are used for dates prior to 1970. +pub fn format_timestamp(timestamp: i64) -> Result<String, time::error::Error> { + let format = format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]"); + let ts = time::OffsetDateTime::from_unix_timestamp(timestamp)?; + let formatted = ts.format(&format)?; + Ok(formatted) +} + +/// Format a x509 ISO timestamp into the provided C buffer. +/// +/// Returns false if an error occurs, otherwise true is returned if +/// the timestamp is properly formatted into the provided buffer. +/// +/// # Safety +/// +/// Access buffers from C that are expected to be valid. +#[no_mangle] +pub unsafe extern "C" fn sc_x509_format_timestamp( + timestamp: i64, buf: *mut c_char, size: usize, +) -> bool { + let timestamp = match format_timestamp(timestamp) { + Ok(ts) => ts, + Err(_) => return false, + }; + crate::ffi::strings::copy_to_c_char(timestamp, buf, size) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_format_timestamp() { + assert_eq!("1969-12-31T00:00:00", format_timestamp(-86400).unwrap()); + assert_eq!("2038-12-31T00:10:03", format_timestamp(2177367003).unwrap()); + } +} |