diff --git a/configure.ac b/configure.ac index 2720c58e2226..05bf2d9b4f29 100644 --- a/configure.ac +++ b/configure.ac @@ -1969,6 +1969,8 @@ dnl AC_ARG_ENABLE(rust, AS_HELP_STRING([--enable-rust], [Enable Rust]), dnl [enable_rust="$enableval"], [enable_rust=no]) AC_ARG_ENABLE([rust], AS_HELP_STRING([--enable-rust], [Enable Experimental Rust support])) + AC_ARG_ENABLE([rust_experimental], AS_HELP_STRING([--enable-rust-experimental], [Enable support for experimental Rus parsers])) + rust_vendor_comment="# " have_rust_vendor="no" @@ -2016,6 +2018,7 @@ fi AM_CONDITIONAL([HAVE_RUST], [test "x$enable_rust" = "xyes"]) + AM_CONDITIONAL([HAVE_RUST_EXTERNAL], [test "x$enable_rust_experimental" = "xyes"]) AC_SUBST(rust_vendor_comment) AM_CONDITIONAL([HAVE_RUST_VENDOR], [test "x$have_rust_vendor" = "xyes"]) @@ -2026,6 +2029,10 @@ echo " for building the distribution" echo " To install: cargo install cargo-vendor" fi + + if test "x$enable_rust_experimental" = "xyes"; then + AC_DEFINE([HAVE_RUST_EXTERNAL],[1],[Enable support for experimental Rust parsers]) + fi fi AM_CONDITIONAL([HAVE_CARGO_VENDOR], [test "x$HAVE_CARGO_VENDOR" != "xno"]) @@ -2141,6 +2148,7 @@ SURICATA_BUILD_CONF="Suricata Configuration: Libnet support: ${enable_libnet} Rust support (experimental): ${enable_rust} + Experimental Rust parsers: ${enable_rust_experimental} Suricatasc install: ${enable_python} diff --git a/rules/Makefile.am b/rules/Makefile.am index 98a484d3a5b2..daa8d67e5235 100644 --- a/rules/Makefile.am +++ b/rules/Makefile.am @@ -8,4 +8,5 @@ tls-events.rules \ modbus-events.rules \ app-layer-events.rules \ files.rules \ -dnp3-events.rules +dnp3-events.rules \ +ntp-events.rules diff --git a/rules/ntp-events.rules b/rules/ntp-events.rules new file mode 100644 index 000000000000..f07a6a125176 --- /dev/null +++ b/rules/ntp-events.rules @@ -0,0 +1,9 @@ +# NTP app layer event rules +# +# SID's fall in the 2222000+ range. See https://redmine.openinfosecfoundation.org/projects/suricata/wiki/AppLayer +# +# These sigs fire at most once per connection. +# +alert ntp any any -> any any (msg:"SURICATA NTP malformed request data"; flow:to_server; app-layer-event:ntp.malformed_data; classtype:protocol-command-decode; sid:2222000; rev:1;) +alert ntp any any -> any any (msg:"SURICATA NTP malformed response data"; flow:to_client; app-layer-event:ntp.malformed_data; classtype:protocol-command-decode; sid:2222001; rev:1;) + diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index a04b0b0d6a04..f10cd3c3e9d3 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -10,8 +10,11 @@ debug = true [features] lua = [] +experimental = ["ntp-parser"] [dependencies] nom = "~3.0" libc = "~0.2.0" crc = "~1.4.0" + +ntp-parser = { version = "^0", optional = true } diff --git a/rust/Makefile.am b/rust/Makefile.am index 8b67c7bed3fe..d4a6cd2c13d1 100644 --- a/rust/Makefile.am +++ b/rust/Makefile.am @@ -25,6 +25,10 @@ if HAVE_LUA FEATURES += lua endif +if HAVE_RUST_EXTERNAL +FEATURES += experimental +endif + all-local: if HAVE_PYTHON cd $(top_srcdir)/rust && CARGO_TARGET_DIR=$(abs_builddir)/target \ diff --git a/rust/gen-c-headers.py b/rust/gen-c-headers.py index fe96e3fd2fa1..cb46f783dfa9 100755 --- a/rust/gen-c-headers.py +++ b/rust/gen-c-headers.py @@ -59,6 +59,7 @@ "libc::c_void": "void", + "libc::c_char": "char", "libc::c_int": "int", "c_int": "int", "libc::int8_t": "int8_t", @@ -77,10 +78,13 @@ "DNSTransaction": "RSDNSTransaction", "NFSState": "NFSState", "NFSTransaction": "NFSTransaction", + "NTPState": "NTPState", + "NTPTransaction": "NTPTransaction", "JsonT": "json_t", "DetectEngineState": "DetectEngineState", "core::DetectEngineState": "DetectEngineState", "core::AppLayerDecoderEvents": "AppLayerDecoderEvents", + "core::AppLayerEventType": "AppLayerEventType", "CLuaState": "lua_State", "Store": "Store", } @@ -99,12 +103,14 @@ def convert_type(rs_type): if mod in [ "*mut", "* mut", - "*const", - "* const", "&mut", "&'static mut", ]: return "%s *" % (type_map[rtype]) + elif mod in [ + "*const", + "* const"]: + return "const %s *" % (type_map[rtype]) elif mod in [ "*mut *const", "*mut*const"]: diff --git a/rust/src/core.rs b/rust/src/core.rs index e3647673c686..dc70ab0a001d 100644 --- a/rust/src/core.rs +++ b/rust/src/core.rs @@ -26,6 +26,11 @@ pub enum Flow {} pub enum DetectEngineState {} pub enum AppLayerDecoderEvents {} +// From app-layer-events.h +pub type AppLayerEventType = libc::c_int; +pub const APP_LAYER_EVENT_TYPE_TRANSACTION : i32 = 1; +pub const APP_LAYER_EVENT_TYPE_PACKET : i32 = 2; + // From stream.h. pub const STREAM_TOSERVER: u8 = 0x04; pub const STREAM_TOCLIENT: u8 = 0x08; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 8e086bae6d39..08e891ffba30 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -39,3 +39,6 @@ pub mod lua; pub mod dns; pub mod nfs; + +#[cfg(feature = "experimental")] +pub mod ntp; diff --git a/rust/src/ntp/mod.rs b/rust/src/ntp/mod.rs new file mode 100644 index 000000000000..35f1a70f2e1b --- /dev/null +++ b/rust/src/ntp/mod.rs @@ -0,0 +1,20 @@ +/* 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 Pierre Chifflier + +pub mod ntp; diff --git a/rust/src/ntp/ntp.rs b/rust/src/ntp/ntp.rs new file mode 100644 index 000000000000..3405ec5e26d2 --- /dev/null +++ b/rust/src/ntp/ntp.rs @@ -0,0 +1,393 @@ +/* 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 Pierre Chifflier + +extern crate ntp_parser; +use self::ntp_parser::*; +use core; +use applayer; +use libc; +use std; +use std::ffi::CStr; + +use log::*; + +use nom::IResult; + +#[repr(u32)] +pub enum NTPEvent { + UnsolicitedResponse = 0, + MalformedData, + NotRequest, + NotResponse, +} + + + +pub struct NTPState { + /// List of transactions for this session + transactions: Vec, + + /// Detection engine states counter + de_state_count: u64, + + /// Events counter + events: u16, + + /// tx counter for assigning incrementing id's to tx's + tx_id: u64, +} + +#[derive(Debug)] +pub struct NTPTransaction { + /// The NTP reference ID + pub xid: u32, + + /// The internal transaction id + id: u64, + + /// The detection engine state, if present + de_state: Option<*mut core::DetectEngineState>, + + /// The events associated with this transaction + events: *mut core::AppLayerDecoderEvents, + + logged: applayer::LoggerFlags, +} + + + +impl NTPState { + pub fn new() -> NTPState { + NTPState{ + transactions: Vec::new(), + de_state_count: 0, + events: 0, + tx_id: 0, + } + } +} + +impl NTPState { + /// Parse an NTP request message + /// + /// Returns The number of messages parsed, or -1 on error + fn parse(&mut self, i: &[u8], _direction: u8) -> i8 { + match parse_ntp(i) { + IResult::Done(_,ref msg) => { + // SCLogDebug!("parse_ntp: {:?}",msg); + if msg.mode == 1 || msg.mode == 3 { + let mut tx = self.new_tx(); + // use the reference id as identifier + tx.xid = msg.ref_id; + self.transactions.push(tx); + } + 1 + }, + IResult::Incomplete(_) => { + SCLogDebug!("Insufficient data while parsing NTP data"); + self.set_event(NTPEvent::MalformedData); + -1 + }, + IResult::Error(_) => { + 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) -> NTPTransaction { + self.tx_id += 1; + NTPTransaction::new(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(|ref tx| tx.id == tx_id + 1); + debug_assert!(tx != None); + 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() { + let ev = event as u8; + core::sc_app_layer_decoder_events_set_event_raw(&mut tx.events, ev); + self.events += 1; + } + } +} + +impl NTPTransaction { + pub fn new(id: u64) -> NTPTransaction { + NTPTransaction { + xid: 0, + id: id, + de_state: None, + events: std::ptr::null_mut(), + logged: applayer::LoggerFlags::new(), + } + } + + fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + } +} + +impl Drop for NTPTransaction { + fn drop(&mut self) { + self.free(); + } +} + + +/// TOSERVER probe function +#[no_mangle] +pub extern "C" fn rs_ntp_probe(input: *const libc::uint8_t, len: libc::uint32_t) + -> libc::int8_t +{ + let slice: &[u8] = unsafe { + std::slice::from_raw_parts(input as *mut u8, len as usize) + }; + match parse_ntp(slice) { + IResult::Done(_, ref msg) => { + if msg.version == 3 || msg.version == 4 { + return 1; + } else { + return -1; + } + }, + IResult::Incomplete(_) => { + return 0; + }, + IResult::Error(_) => { + return -1; + }, + } +} + + + + + + + +/// Returns *mut NTPState +#[no_mangle] +pub extern "C" fn rs_ntp_state_new() -> *mut libc::c_void { + let state = NTPState::new(); + let boxed = Box::new(state); + return unsafe{std::mem::transmute(boxed)}; +} + +/// Params: +/// - state: *mut NTPState as void pointer +#[no_mangle] +pub extern "C" fn rs_ntp_state_free(state: *mut libc::c_void) { + // Just unbox... + let mut ntp_state: Box = unsafe{std::mem::transmute(state)}; + ntp_state.free(); +} + +#[no_mangle] +pub extern "C" fn rs_ntp_parse_request(_flow: *const core::Flow, + state: &mut NTPState, + _pstate: *const libc::c_void, + input: *const libc::uint8_t, + input_len: u32, + _data: *const libc::c_void) -> i8 { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + state.parse(buf, 0) +} + +#[no_mangle] +pub extern "C" fn rs_ntp_parse_response(_flow: *const core::Flow, + state: &mut NTPState, + _pstate: *const libc::c_void, + input: *const libc::uint8_t, + input_len: u32, + _data: *const libc::c_void) -> i8 { + let buf = unsafe{std::slice::from_raw_parts(input, input_len as usize)}; + state.parse(buf, 1) +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_get_tx(state: &mut NTPState, + tx_id: libc::uint64_t) + -> *mut NTPTransaction +{ + match state.get_tx_by_id(tx_id) { + Some(tx) => unsafe{std::mem::transmute(tx)}, + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_get_tx_count(state: &mut NTPState) + -> libc::uint64_t +{ + state.tx_id +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_tx_free(state: &mut NTPState, + tx_id: libc::uint64_t) +{ + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_progress_completion_status( + _direction: libc::uint8_t) + -> libc::c_int +{ + return 1; +} + +#[no_mangle] +pub extern "C" fn rs_ntp_tx_get_alstate_progress(_tx: &mut NTPTransaction, + _direction: libc::uint8_t) + -> libc::uint8_t +{ + 1 +} + + + + + +#[no_mangle] +pub extern "C" fn rs_ntp_tx_set_logged(_state: &mut NTPState, + tx: &mut NTPTransaction, + logger: libc::uint32_t) +{ + tx.logged.set_logged(logger); +} + +#[no_mangle] +pub extern "C" fn rs_ntp_tx_get_logged(_state: &mut NTPState, + tx: &mut NTPTransaction, + logger: libc::uint32_t) + -> i8 +{ + if tx.logged.is_logged(logger) { + return 1; + } + return 0; +} + + +#[no_mangle] +pub extern "C" fn rs_ntp_state_set_tx_detect_state( + state: &mut NTPState, + tx: &mut NTPTransaction, + de_state: &mut core::DetectEngineState) +{ + state.de_state_count += 1; + tx.de_state = Some(de_state); +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_get_tx_detect_state( + tx: &mut NTPTransaction) + -> *mut core::DetectEngineState +{ + match tx.de_state { + Some(ds) => ds, + None => std::ptr::null_mut(), + } +} + + +#[no_mangle] +pub extern "C" fn rs_ntp_state_has_events(state: &mut NTPState) -> u8 { + if state.events > 0 { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_get_events(state: &mut NTPState, + tx_id: libc::uint64_t) + -> *mut core::AppLayerDecoderEvents +{ + match state.get_tx_by_id(tx_id) { + Some(tx) => tx.events, + _ => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub extern "C" fn rs_ntp_state_get_event_info(event_name: *const libc::c_char, + event_id: *mut libc::c_int, + event_type: *mut core::AppLayerEventType) + -> i8 +{ + if event_name == std::ptr::null() { return -1; } + let c_event_name: &CStr = unsafe { CStr::from_ptr(event_name) }; + let event = match c_event_name.to_str() { + Ok(s) => { + match s { + "malformed_data" => NTPEvent::MalformedData as i32, + _ => -1, // unknown event + } + }, + Err(_) => -1, // UTF-8 conversion failed + }; + unsafe{ + *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION; + *event_id = event as libc::c_int; + }; + 0 +} + +#[cfg(test)] +mod tests { + use super::NTPState; + + #[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!(1, state.parse(REQ, 0)); + } +} diff --git a/src/Makefile.am b/src/Makefile.am index 1769b3b0ca7f..09fcafae2d67 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -41,6 +41,7 @@ app-layer-smb.c app-layer-smb.h \ app-layer-smtp.c app-layer-smtp.h \ app-layer-nfs-tcp.c app-layer-nfs-tcp.h \ app-layer-nfs-udp.c app-layer-nfs-udp.h \ +app-layer-ntp.c app-layer-ntp.h \ app-layer-template.c app-layer-template.h \ app-layer-ssh.c app-layer-ssh.h \ app-layer-ssl.c app-layer-ssl.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index 1a0abdfb7c43..c0c0d21678d0 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -698,6 +698,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_ENIP\n"); else if (pp_pe->alproto == ALPROTO_NFS) printf(" alproto: ALPROTO_NFS\n"); + else if (pp_pe->alproto == ALPROTO_NTP) + printf(" alproto: ALPROTO_NTP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) @@ -757,6 +759,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_ENIP\n"); else if (pp_pe->alproto == ALPROTO_NFS) printf(" alproto: ALPROTO_NFS\n"); + else if (pp_pe->alproto == ALPROTO_NTP) + printf(" alproto: ALPROTO_NTP\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) diff --git a/src/app-layer-ntp.c b/src/app-layer-ntp.c new file mode 100644 index 000000000000..599ed78403fe --- /dev/null +++ b/src/app-layer-ntp.c @@ -0,0 +1,307 @@ +/* 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. + */ + +/** + * \file + * + * \author Pierre Chifflier + * + * Parser for NTP application layer running on UDP port 123. + */ + +#include "suricata-common.h" +#include "stream.h" +#include "conf.h" + +#include "util-unittest.h" + +#include "app-layer-detect-proto.h" +#include "app-layer-parser.h" + +#include "app-layer-ntp.h" + +#if defined(HAVE_RUST) && defined(HAVE_RUST_EXTERNAL) + +#include "rust-ntp-ntp-gen.h" + +/* The default port to probe for NTP traffic if not provided in the + * configuration file. */ +#define NTP_DEFAULT_PORT "123" + +/* The minimum size for an NTP message. */ +#define NTP_MIN_FRAME_LEN 2 + + +static void *NTPStateAlloc(void) +{ + return rs_ntp_state_new(); +} + +static void NTPStateFree(void *state) +{ + rs_ntp_state_free(state); +} + +/** + * \brief Callback from the application layer to have a transaction freed. + * + * \param state a void pointer to the NTPState object. + * \param tx_id the transaction ID to free. + */ +static void NTPStateTxFree(void *state, uint64_t tx_id) +{ + rs_ntp_state_tx_free(state, tx_id); +} + +static int NTPStateGetEventInfo(const char *event_name, int *event_id, + AppLayerEventType *event_type) +{ + return rs_ntp_state_get_event_info(event_name, event_id, event_type); +} + +static AppLayerDecoderEvents *NTPGetEvents(void *state, uint64_t tx_id) +{ + return rs_ntp_state_get_events(state, tx_id); +} + +static int NTPHasEvents(void *state) +{ + return rs_ntp_state_has_events(state); +} + +/** + * \brief Probe the input to see if it looks like NTP. + * + * \retval ALPROTO_NTP if it looks like NTP, otherwise + * ALPROTO_UNKNOWN. + */ +static AppProto NTPProbingParser(uint8_t *input, uint32_t input_len, + uint32_t *offset) +{ + if (input_len < NTP_MIN_FRAME_LEN) { + return ALPROTO_UNKNOWN; + } + + int8_t r = rs_ntp_probe(input, input_len); + if (r == 1) { + return ALPROTO_NTP; + } else if (r == -1) { + return ALPROTO_FAILED; + } + + SCLogDebug("Protocol not detected as ALPROTO_NTP."); + return ALPROTO_UNKNOWN; +} + +static int RustNTPParseRequest(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustNTPParseRequest"); + return rs_ntp_parse_request(f, state, pstate, input, input_len, + local_data); +} + +static int RustNTPParseResponse(Flow *f, void *state, + AppLayerParserState *pstate, uint8_t *input, uint32_t input_len, + void *local_data) +{ + SCLogDebug("RustNTPParseResponse"); + return rs_ntp_parse_response(f, state, pstate, input, input_len, + local_data); +} + +static uint64_t NTPGetTxCnt(void *state) +{ + return rs_ntp_state_get_tx_count(state); +} + +static void *NTPGetTx(void *state, uint64_t tx_id) +{ + return rs_ntp_state_get_tx(state, tx_id); +} + +// static void NTPSetTxLogged(void *state, void *vtx, uint32_t logger) +// { +// rs_ntp_tx_set_logged(state, vtx, logger); +// } +// +// static int NTPGetTxLogged(void *state, void *vtx, uint32_t logger) +// { +// return rs_ntp_tx_get_logged(state, vtx, logger); +// } + + + + + + + +/** + * \brief Called by the application layer. + * + * In most cases 1 can be returned here. + */ +static int NTPGetAlstateProgressCompletionStatus(uint8_t direction) { + return rs_ntp_state_progress_completion_status(direction); +} + +/** + * \brief Return the state of a transaction in a given direction. + */ +static int NTPGetStateProgress(void *tx, uint8_t direction) +{ + return rs_ntp_tx_get_alstate_progress(tx, direction); +} + +/** + * \brief Get stored Tx detect state + */ +static DetectEngineState *NTPGetTxDetectState(void *vtx) +{ + return rs_ntp_state_get_tx_detect_state(vtx); +} + +/** + * \brief Set stored Tx detect state + */ +static int NTPSetTxDetectState(void *state, void *vtx, + DetectEngineState *s) +{ + rs_ntp_state_set_tx_detect_state(state, vtx, s); + return 0; +} + +void RegisterNTPParsers(void) +{ + const char *proto_name = "ntp"; + + /* Check if NTP UDP detection is enabled. If it does not exist in + * the configuration file then it will be enabled by default. */ + if (AppLayerProtoDetectConfProtoDetectionEnabled("udp", proto_name)) { + + SCLogDebug("NTP UDP protocol detection enabled."); + + AppLayerProtoDetectRegisterProtocol(ALPROTO_NTP, proto_name); + + if (RunmodeIsUnittests()) { + + SCLogDebug("Unittest mode, registeringd default configuration."); + AppLayerProtoDetectPPRegister(IPPROTO_UDP, NTP_DEFAULT_PORT, + ALPROTO_NTP, 0, NTP_MIN_FRAME_LEN, STREAM_TOSERVER, + NTPProbingParser, NULL); + + } + else { + + if (!AppLayerProtoDetectPPParseConfPorts("udp", IPPROTO_UDP, + proto_name, ALPROTO_NTP, 0, NTP_MIN_FRAME_LEN, + NTPProbingParser, NULL)) { + SCLogDebug("No NTP app-layer configuration, enabling NTP" + " detection UDP detection on port %s.", + NTP_DEFAULT_PORT); + AppLayerProtoDetectPPRegister(IPPROTO_UDP, + NTP_DEFAULT_PORT, ALPROTO_NTP, 0, + NTP_MIN_FRAME_LEN, STREAM_TOSERVER, + NTPProbingParser, NULL); + } + + } + + } + + else { + SCLogDebug("Protocol detecter and parser disabled for NTP."); + return; + } + + if (AppLayerParserConfParserEnabled("udp", proto_name)) { + + SCLogDebug("Registering NTP protocol parser."); + + /* Register functions for state allocation and freeing. A + * state is allocated for every new NTP flow. */ + AppLayerParserRegisterStateFuncs(IPPROTO_UDP, ALPROTO_NTP, + NTPStateAlloc, NTPStateFree); + + /* Register request parser for parsing frame from server to client. */ + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_NTP, + STREAM_TOSERVER, RustNTPParseRequest); + + /* Register response parser for parsing frames from server to client. */ + AppLayerParserRegisterParser(IPPROTO_UDP, ALPROTO_NTP, + STREAM_TOCLIENT, RustNTPParseResponse); + + /* Register a function to be called by the application layer + * when a transaction is to be freed. */ + AppLayerParserRegisterTxFreeFunc(IPPROTO_UDP, ALPROTO_NTP, + NTPStateTxFree); + + // AppLayerParserRegisterLoggerFuncs(IPPROTO_UDP, ALPROTO_NTP, + // NTPGetTxLogged, NTPSetTxLogged); + + /* Register a function to return the current transaction count. */ + AppLayerParserRegisterGetTxCnt(IPPROTO_UDP, ALPROTO_NTP, + NTPGetTxCnt); + + /* Transaction handling. */ + AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_NTP, + NTPGetAlstateProgressCompletionStatus); + AppLayerParserRegisterGetStateProgressFunc(IPPROTO_UDP, + ALPROTO_NTP, NTPGetStateProgress); + AppLayerParserRegisterGetTx(IPPROTO_UDP, ALPROTO_NTP, + NTPGetTx); + + /* Application layer event handling. */ + AppLayerParserRegisterHasEventsFunc(IPPROTO_UDP, ALPROTO_NTP, + NTPHasEvents); + + /* What is this being registered for? */ + AppLayerParserRegisterDetectStateFuncs(IPPROTO_UDP, ALPROTO_NTP, + NULL, NTPGetTxDetectState, NTPSetTxDetectState); + + AppLayerParserRegisterGetEventInfo(IPPROTO_UDP, ALPROTO_NTP, + NTPStateGetEventInfo); + AppLayerParserRegisterGetEventsFunc(IPPROTO_UDP, ALPROTO_NTP, + NTPGetEvents); + } + else { + SCLogDebug("NTP protocol parsing disabled."); + } + +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests(IPPROTO_UDP, ALPROTO_NTP, + NTPParserRegisterTests); +#endif +} + +#ifdef UNITTESTS +#endif + +void NTPParserRegisterTests(void) +{ +#ifdef UNITTESTS +#endif +} + +#else /* HAVE_RUST */ + +void RegisterNTPParsers(void) +{ +} + +#endif /* HAVE_RUST */ diff --git a/src/app-layer-ntp.h b/src/app-layer-ntp.h new file mode 100644 index 000000000000..21606395509c --- /dev/null +++ b/src/app-layer-ntp.h @@ -0,0 +1,34 @@ +/* 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. + */ + +/** + * \file + * + * \author Pierre Chifflier + */ + +#ifndef __APP_LAYER_NTP_H__ +#define __APP_LAYER_NTP_H__ + +void RegisterNTPParsers(void); +void NTPParserRegisterTests(void); + +/** Opaque Rust types. */ +typedef struct NTPState_ NTPState; +typedef struct NTPTransaction_ NTPTransaction; + +#endif /* __APP_LAYER_NTP_H__ */ diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 27f35555e499..925fea84d69f 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -62,6 +62,7 @@ #include "app-layer-dnp3.h" #include "app-layer-nfs-tcp.h" #include "app-layer-nfs-udp.h" +#include "app-layer-ntp.h" #include "app-layer-template.h" #include "conf.h" @@ -1386,6 +1387,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterDNP3Parsers(); RegisterNFSTCPParsers(); RegisterNFSUDPParsers(); + RegisterNTPParsers(); RegisterTemplateParsers(); /** IMAP */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index 945d34f58c2c..fdcaff298ac0 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -84,6 +84,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_NFS: proto_name = "nfs"; break; + case ALPROTO_NTP: + proto_name = "ntp"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index dac94cc9ff15..b765aab7d834 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -45,6 +45,7 @@ enum AppProtoEnum { ALPROTO_ENIP, ALPROTO_DNP3, ALPROTO_NFS, + ALPROTO_NTP, ALPROTO_TEMPLATE, /* used by the probing parser when alproto detection fails diff --git a/suricata.yaml.in b/suricata.yaml.in index 990d46e20cfb..4698f544e891 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -103,6 +103,7 @@ rule-files: # - modbus-events.rules # available in suricata sources under rules dir # - app-layer-events.rules # available in suricata sources under rules dir # - dnp3-events.rules # available in suricata sources under rules dir +# - ntp-events.rules # available in suricata sources under rules dir classification-file: @e_sysconfdir@classification.config reference-config-file: @e_sysconfdir@reference.config @@ -679,6 +680,8 @@ pcap-file: # "detection-only" enables protocol detection only (parser disabled). app-layer: protocols: + ntp: + enabled: yes nfs: enabled: yes tls: