diff --git a/etc/schema.json b/etc/schema.json index b7455a493d7f..cfa2aa812c5c 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -5786,6 +5786,10 @@ "description": "Errors encountered parsing SIP/UDP protocol", "$ref": "#/$defs/stats_applayer_error" }, + "sslproxy": { + "description": "Errors encountered parsing SSLproxy protocol", + "$ref": "#/$defs/stats_applayer_error" + }, "smb": { "description": "Errors encountered parsing SMB protocol", "$ref": "#/$defs/stats_applayer_error" @@ -5969,6 +5973,10 @@ "type": "integer", "description": "Number of flows for SIP/UDP protocol" }, + "sslproxy": { + "type": "integer", + "description": "Number of flows for SSLproxy protocol" + }, "smb": { "type": "integer", "description": "Number of flows for SMB protocol" @@ -6143,6 +6151,10 @@ "type": "integer", "description": "Number of transactions for SIP/UDP protocol" }, + "sslproxy": { + "type": "integer", + "description": "Number of transactions for SSLproxy protocol" + }, "smb": { "type": "integer", "description": "Number of transactions for SMB protocol" diff --git a/rust/src/applayer.rs b/rust/src/applayer.rs index c7a434c60ef7..8d45eabc3f62 100644 --- a/rust/src/applayer.rs +++ b/rust/src/applayer.rs @@ -346,6 +346,15 @@ impl AppLayerResult { pub fn ok() -> Self { Default::default() } + /// parser has successfully processed input, but not all. The rest should be + /// immediately be processed. + pub fn ok_partial_continue(consumed: u32) -> Self { + return Self { + status: 2, + consumed, + needed: 0, + }; + } /// parser has hit an unrecoverable error. Returning this to the API /// leads to no further calls to the parser. pub fn err() -> Self { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index b53f78bfb5a1..1c75cfcd2340 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -120,6 +120,7 @@ pub mod telnet; pub mod websocket; pub mod enip; pub mod pop3; +pub mod sslproxy; pub mod applayertemplate; pub mod rdp; pub mod x509; diff --git a/rust/src/sslproxy/mod.rs b/rust/src/sslproxy/mod.rs new file mode 100644 index 000000000000..249d23bb264d --- /dev/null +++ b/rust/src/sslproxy/mod.rs @@ -0,0 +1,21 @@ +/* Copyright (C) 2025 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 sslproxy parser and logger module. + +mod parser; +pub mod sslproxy; diff --git a/rust/src/sslproxy/parser.rs b/rust/src/sslproxy/parser.rs new file mode 100644 index 000000000000..3fd3a19362ec --- /dev/null +++ b/rust/src/sslproxy/parser.rs @@ -0,0 +1,69 @@ +/* Copyright (C) 2025 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_until}, + number::streaming::{le_u8}, + character::complete::{digit1}, + combinator::map_res, + IResult, +}; +use std; +use std::str; +use std::str::FromStr; +use std::net::IpAddr; + +#[derive(Debug, PartialEq, Eq)] +pub struct SSLproxyHeader<> { + pub ip1: IpAddr, + pub port1: u16, + pub ip2: IpAddr, + pub port2: u16, + pub ip3: IpAddr, + pub port3: u16, + pub opt: u8, +} + +// SSLproxy: [127.0.0.1]:44627,[192.168.0.30]:54116,[83.215.238.28]:465,s +pub fn parse_message(i: &[u8]) -> IResult<&[u8], SSLproxyHeader<>> { + let (i, _hdr) = tag(b"SSLproxy: [")(i)?; + let (i, body) = take_until("\r\n")(i)?; + let (x, ip1) = map_res(map_res(take_until("]:"), std::str::from_utf8), IpAddr::from_str,)(body)?; + let (x, _) = tag(b"]:")(x)?; + let (x, port1) = map_res(map_res(digit1, str::from_utf8), u16::from_str)(x)?; + let (x, _) = tag(b",[")(x)?; + let (x, ip2) = map_res(map_res(take_until("]:"), std::str::from_utf8), IpAddr::from_str,)(x)?; + let (x, _) = tag(b"]:")(x)?; + let (x, port2) = map_res(map_res(digit1, str::from_utf8), u16::from_str)(x)?; + let (x, _) = tag(b",[")(x)?; + let (x, ip3) = map_res(map_res(take_until("]:"), std::str::from_utf8), IpAddr::from_str,)(x)?; + let (x, _) = tag(b"]:")(x)?; + let (x, port3) = map_res(map_res(digit1, str::from_utf8), u16::from_str)(x)?; + let (x, _) = tag(b",")(x)?; + let (_, opt) = le_u8(x)?; + + let r = SSLproxyHeader { + ip1, + port1, + ip2, + port2, + ip3, + port3, + opt, + }; + Ok((i, r)) +} diff --git a/rust/src/sslproxy/sslproxy.rs b/rust/src/sslproxy/sslproxy.rs new file mode 100644 index 000000000000..c19456d56477 --- /dev/null +++ b/rust/src/sslproxy/sslproxy.rs @@ -0,0 +1,313 @@ +/* Copyright (C) 2025 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::{ALPROTO_UNKNOWN, IPPROTO_TCP, sc_app_layer_parser_trigger_raw_stream_inspection}; +use crate::flow::Flow; +use crate::direction::Direction; +use nom7 as nom; +use std; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; +use std::net::{IpAddr}; +use suricata_sys::sys::{ + AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled, + SCAppLayerParserStateIssetFlag, + SCAppLayerProtoDetectConfProtoDetectionEnabled, + SCAppLayerProtoDetectPMRegisterPatternCS, + SCAppLayerRequestProtocolChangeUnknown, + SCFlowSetDecrypted, +}; + +pub(super) static mut ALPROTO_SSLPROXY: AppProto = ALPROTO_UNKNOWN; + +#[derive(AppLayerEvent)] +enum SSLProxyEvent { + TooManyTransactions, +} + +pub struct SSLProxyTransaction { + tx_id: u64, + tx_data: AppLayerTxData, +} + +impl Default for SSLProxyTransaction { + fn default() -> Self { + Self::new() + } +} + +impl SSLProxyTransaction { + pub fn new() -> SSLProxyTransaction { + Self { + tx_id: 0, + tx_data: AppLayerTxData::new(), + } + } +} + +impl Transaction for SSLProxyTransaction { + fn id(&self) -> u64 { + self.tx_id + } +} + +#[derive(Default)] +pub struct SSLProxyState { + state_data: AppLayerStateData, + tx_id: u64, + transaction: SSLProxyTransaction, +} + +impl State for SSLProxyState { + fn get_transaction_count(&self) -> usize { + 1_usize + } + + fn get_transaction_by_index(&self, index: usize) -> Option<&SSLProxyTransaction> { + if index == 0 { + return Some(&self.transaction); + } + None + } +} + +impl SSLProxyState { + pub fn new() -> Self { + Default::default() + } + + pub fn get_tx(&mut self, tx_id: u64) -> Option<&SSLProxyTransaction> { + if tx_id == 0 { + return Some(&self.transaction); + } + return None; + } + + fn parse_request(&mut self, flow: *mut Flow, input: &[u8]) -> AppLayerResult { + + SCLogDebug!("input {}", input.len()); + // We're not interested in empty requests. + if input.is_empty() { + return AppLayerResult::ok(); + } + + match parser::parse_message(input) { + Ok((rem, request)) => { + SCLogDebug!("Request: {:?}", request); + let mut _tx = self.get_tx(0); + + let proto = 6; + let sp = request.port2; + let dp = request.port3; + + if let IpAddr::V4(src_ip_v4) = request.ip2 { + let src_ip : u32 = src_ip_v4.into(); + let src_ip = src_ip.to_be(); + if let IpAddr::V4(dst_ip_v4) = request.ip3 { + let dest_ip : u32 = dst_ip_v4.into(); + let dest_ip = dest_ip.to_be(); + unsafe { + SCAppLayerRequestProtocolChangeUnknown(flow, request.port3); + sc_app_layer_parser_trigger_raw_stream_inspection(flow, Direction::ToServer as i32); + SCFlowSetDecrypted(flow, proto, src_ip, sp, dest_ip, dp); + } + + if !rem.is_empty() { + SCLogDebug!("returning partial"); + let consumed = (input.len() - rem.len()) + 2; + return AppLayerResult::ok_partial_continue(consumed as u32); + } + } + } + SCLogDebug!("malformed proxy line"); + return AppLayerResult::err(); + } + Err(nom::Err::Incomplete(_)) => { + SCLogDebug!("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(); + let needed = consumed + 1; + return AppLayerResult::incomplete(consumed as u32, needed as u32); + } + Err(_e) => { + SCLogDebug!("e {:?}", _e); + return AppLayerResult::err(); + } + } + } + + fn parse_response(&mut self, input: &[u8]) -> AppLayerResult { + /* this parser should never get response data */ + if !input.is_empty() { + return AppLayerResult::err(); + } + return AppLayerResult::ok(); + } +} + +// C exports. + +extern "C" fn sslproxy_state_new(_orig_state: *mut c_void, _orig_proto: AppProto) -> *mut c_void { + let state = SSLProxyState::new(); + let boxed = Box::new(state); + return Box::into_raw(boxed) as *mut c_void; +} + +unsafe extern "C" fn sslproxy_state_free(state: *mut c_void) { + std::mem::drop(Box::from_raw(state as *mut SSLProxyState)); +} + +unsafe extern "C" fn sslproxy_state_tx_free(_state: *mut c_void, _tx_id: u64) { +} + +unsafe extern "C" fn sslproxy_parse_request( + flow: *mut Flow, state: *mut c_void, pstate: *mut AppLayerParserState, + stream_slice: StreamSlice, _data: *const c_void, +) -> AppLayerResult { + let eof = SCAppLayerParserStateIssetFlag(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, SSLProxyState); + + let buf = stream_slice.as_slice(); + state.parse_request(flow, buf) +} + +unsafe extern "C" fn sslproxy_parse_response( + _flow: *mut Flow, state: *mut c_void, pstate: *mut AppLayerParserState, + stream_slice: StreamSlice, _data: *const c_void, +) -> AppLayerResult { + let _eof = SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; + let state = cast_pointer!(state, SSLProxyState); + + let buf = stream_slice.as_slice(); + state.parse_response(buf) +} + +unsafe extern "C" fn sslproxy_state_get_tx(state: *mut c_void, tx_id: u64) -> *mut c_void { + let state = cast_pointer!(state, SSLProxyState); + match state.get_tx(tx_id) { + Some(tx) => { + return tx as *const _ as *mut _; + } + None => { + return std::ptr::null_mut(); + } + } +} + +unsafe extern "C" fn sslproxy_state_get_tx_count(state: *mut c_void) -> u64 { + let state = cast_pointer!(state, SSLProxyState); + return state.tx_id; +} + +unsafe extern "C" fn sslproxy_tx_get_alstate_progress(_tx: *mut c_void, _direction: u8) -> c_int { + return 1; +} + +export_tx_data_get!(sslproxy_get_tx_data, SSLProxyTransaction); +export_state_data_get!(sslproxy_get_state_data, SSLProxyState); + +fn register_pattern_probe(proto: u8) -> i8 { + let methods: Vec<&str> = vec![ + "SSLproxy:\0", + ]; + let mut r = 0; + unsafe { + for method in methods { + let depth = (method.len() - 1) as u16; + r |= SCAppLayerProtoDetectPMRegisterPatternCS( + proto, + ALPROTO_SSLPROXY, + method.as_ptr() as *const std::os::raw::c_char, + depth, + 0, + Direction::ToServer as u8, + ); + } + } + + if r == 0 { + return 0; + } else { + return -1; + } +} + +// Parser name as a C style string. +const PARSER_NAME: &[u8] = b"sslproxy\0"; + +#[no_mangle] +pub unsafe extern "C" fn SCRegisterSSLProxyParser() { + let parser = RustParser { + name: PARSER_NAME.as_ptr() as *const c_char, + default_port: std::ptr::null(), + ipproto: IPPROTO_TCP, + probe_ts: None, + probe_tc: None, + min_depth: 0, + max_depth: 16, + state_new: sslproxy_state_new, + state_free: sslproxy_state_free, + tx_free: sslproxy_state_tx_free, + parse_ts: sslproxy_parse_request, + parse_tc: sslproxy_parse_response, + get_tx_count: sslproxy_state_get_tx_count, + get_tx: sslproxy_state_get_tx, + tx_comp_st_ts: 1, + tx_comp_st_tc: 1, + tx_get_progress: sslproxy_tx_get_alstate_progress, + get_eventinfo: Some(SSLProxyEvent::get_event_info), + get_eventinfo_byid: Some(SSLProxyEvent::get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_tx_files: None, + get_tx_iterator: Some(state_get_tx_iterator::), + get_tx_data: sslproxy_get_tx_data, + get_state_data: sslproxy_get_state_data, + apply_tx_config: None, + flags: 0, // no GAPS + get_frame_id_by_name: None, + get_frame_name_by_id: None, + get_state_id_by_name: None, + get_state_name_by_id: None, + }; + + let ip_proto_str = CString::new("tcp").unwrap(); + + if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let alproto = AppLayerRegisterProtocolDetection(&parser, 1); + ALPROTO_SSLPROXY = alproto; + if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { + let _ = AppLayerRegisterParser(&parser, alproto); + } + if register_pattern_probe(IPPROTO_TCP) < 0 { + return; + } + SCLogDebug!("Rust sslproxy parser registered."); + } else { + SCLogDebug!("Protocol detector and parser disabled for SSLPROXY."); + } +} diff --git a/rust/sys/src/sys.rs b/rust/sys/src/sys.rs index b44d5400264d..1793ad0d8824 100644 --- a/rust/sys/src/sys.rs +++ b/rust/sys/src/sys.rs @@ -38,14 +38,15 @@ pub enum AppProtoEnum { ALPROTO_WEBSOCKET = 29, ALPROTO_LDAP = 30, ALPROTO_DOH2 = 31, - ALPROTO_TEMPLATE = 32, - ALPROTO_RDP = 33, - ALPROTO_HTTP2 = 34, - ALPROTO_BITTORRENT_DHT = 35, - ALPROTO_POP3 = 36, - ALPROTO_MDNS = 37, - ALPROTO_HTTP = 38, - ALPROTO_MAX_STATIC = 39, + ALPROTO_SSLPROXY = 32, + ALPROTO_TEMPLATE = 33, + ALPROTO_RDP = 34, + ALPROTO_HTTP2 = 35, + ALPROTO_BITTORRENT_DHT = 36, + ALPROTO_POP3 = 37, + ALPROTO_MDNS = 38, + ALPROTO_HTTP = 39, + ALPROTO_MAX_STATIC = 40, } pub type AppProto = u16; extern "C" { @@ -726,6 +727,9 @@ extern "C" { offset: u16, direction: u8, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn SCAppLayerRequestProtocolChangeUnknown(f: *mut Flow, dp: u16) -> bool; +} extern "C" { pub fn SCAppLayerRequestProtocolTLSUpgrade(f: *mut Flow) -> bool; } @@ -934,3 +938,8 @@ extern "C" { extern "C" { pub fn SCFlowGetDestinationPort(flow: *const Flow) -> u16; } +extern "C" { + pub fn SCFlowSetDecrypted( + f: *mut Flow, proto: u8, src_ip: u32, sp: u16, dest_ip: u32, dp: u16, + ) -> ::std::os::raw::c_int; +} diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index dba98e728c4c..3e0ac72a30dc 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -1821,6 +1821,11 @@ bool AppLayerRequestProtocolChange(Flow *f, uint16_t dp, AppProto expect_proto) return true; } +bool SCAppLayerRequestProtocolChangeUnknown(Flow *f, uint16_t dp) +{ + return AppLayerRequestProtocolChange(f, dp, ALPROTO_UNKNOWN); +} + /** \brief request applayer to wrap up this protocol and rerun protocol * detection with expectation of TLS. Used by STARTTLS. * diff --git a/src/app-layer-detect-proto.h b/src/app-layer-detect-proto.h index 0c68d0dcd79f..eb6459b4f136 100644 --- a/src/app-layer-detect-proto.h +++ b/src/app-layer-detect-proto.h @@ -107,6 +107,7 @@ int AppLayerProtoDetectSetup(void); void AppLayerProtoDetectReset(Flow *); bool AppLayerRequestProtocolChange(Flow *f, uint16_t dp, AppProto expect_proto); +bool SCAppLayerRequestProtocolChangeUnknown(Flow *f, uint16_t dp); bool SCAppLayerRequestProtocolTLSUpgrade(Flow *f); void SCAppLayerForceProtocolChange(Flow *f, AppProto new_proto); diff --git a/src/app-layer-imap.c b/src/app-layer-imap.c index ac23751a2c5a..4d1036002c98 100644 --- a/src/app-layer-imap.c +++ b/src/app-layer-imap.c @@ -28,6 +28,11 @@ static int IMAPRegisterPatternsForProtocolDetection(void) { + if (SCAppLayerProtoDetectPMRegisterPatternCI( + IPPROTO_TCP, ALPROTO_IMAP, "+ \r\n", 4, 0, STREAM_TOCLIENT) < 0) { + return -1; + } + if (SCAppLayerProtoDetectPMRegisterPatternCI( IPPROTO_TCP, ALPROTO_IMAP, "* OK ", 5, 0, STREAM_TOCLIENT) < 0) { return -1; @@ -77,7 +82,18 @@ static int IMAPRegisterPatternsForProtocolDetection(void) 17 /*6 for max tag len + space + len(CAPABILITY)*/, 0, STREAM_TOSERVER) < 0) { return -1; } - + if (SCAppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_IMAP, " authenticate", + 17 /*6 for max tag len + space + len(authenticate PLAIN)*/, 0, + STREAM_TOSERVER) < 0) { + return -1; + } +#if 0 + if (SCAppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_IMAP, " authenticate PLAIN", + 25 /*6 for max tag len + space + len(authenticate PLAIN)*/, 0, + STREAM_TOSERVER) < 0) { + return -1; + } +#endif return 0; } diff --git a/src/app-layer-parser.c b/src/app-layer-parser.c index 08757c4c2a27..a5030af442db 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -1302,6 +1302,7 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow uint64_t p_tx_cnt = 0; uint32_t consumed = input_len; const uint8_t direction = (flags & STREAM_TOSERVER) ? 0 : 1; + int r = 1; /* we don't have the parser registered for this protocol */ if (p->StateAlloc == NULL) { @@ -1389,7 +1390,14 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow if (res.status < 0) { AppLayerIncParserErrorCounter(tv, f); goto error; - } else if (res.status > 0) { + } else if (res.status == 2 && res.consumed > 0) { + SCLogDebug("OK consumed %u", res.consumed); + + /* partial */ + SCLogDebug("OK partial %u", res.consumed); + consumed = res.consumed; + r = 2; + } else if (res.status == 1) { DEBUG_VALIDATE_BUG_ON(res.consumed > input_len); DEBUG_VALIDATE_BUG_ON(res.needed + res.consumed < input_len); DEBUG_VALIDATE_BUG_ON(res.needed == 0); @@ -1422,6 +1430,7 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow } } consumed = res.consumed; + r = 1; } } @@ -1479,8 +1488,9 @@ int AppLayerParserParse(ThreadVars *tv, AppLayerParserThreadCtx *alp_tctx, Flow /* update app progress */ if (consumed != input_len && f->proto == IPPROTO_TCP && f->protoctx != NULL) { TcpSession *ssn = f->protoctx; + SCLogDebug("updated progress with %u", consumed); StreamTcpUpdateAppLayerProgress(ssn, direction, consumed); - SCReturnInt(1); + SCReturnInt(r); } SCReturnInt(0); @@ -1800,6 +1810,7 @@ void AppLayerParserRegisterProtocolParsers(void) SCRegisterLdapTcpParser(); SCRegisterLdapUdpParser(); SCRegisterMdnsParser(); + SCRegisterSSLProxyParser(); SCRegisterTemplateParser(); SCRfbRegisterParser(); SCMqttRegisterParser(); diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 2498c744d2f4..130fec41af9c 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -64,6 +64,7 @@ enum AppProtoEnum { ALPROTO_WEBSOCKET, ALPROTO_LDAP, ALPROTO_DOH2, + ALPROTO_SSLPROXY, ALPROTO_TEMPLATE, ALPROTO_RDP, ALPROTO_HTTP2, diff --git a/src/app-layer-smtp.c b/src/app-layer-smtp.c index 7c0274bef6c0..57940d480a83 100644 --- a/src/app-layer-smtp.c +++ b/src/app-layer-smtp.c @@ -1179,7 +1179,8 @@ static int SMTPProcessRequest( state->toserver_data_count += (line->len + line->delim_len); - if (!(state->parser_state & SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) { + if ((f->flags & FLOW_IS_DECRYPTED) == 0 && + !(state->parser_state & SMTP_PARSER_STATE_FIRST_REPLY_SEEN)) { SMTPSetEvent(state, SMTP_DECODER_EVENT_NO_SERVER_WELCOME_MESSAGE); } diff --git a/src/app-layer.c b/src/app-layer.c index d9049a5480bc..56ee99856035 100644 --- a/src/app-layer.c +++ b/src/app-layer.c @@ -387,7 +387,7 @@ extern enum ExceptionPolicy g_applayerparser_error_policy; */ static int TCPProtoDetect(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, AppLayerThreadCtx *app_tctx, Packet *p, Flow *f, TcpSession *ssn, TcpStream **stream, - uint8_t *data, uint32_t data_len, uint8_t flags, enum StreamUpdateDir app_update_dir) + const uint8_t *data, uint32_t data_len, uint8_t flags, enum StreamUpdateDir app_update_dir) { AppProto *alproto; AppProto *alproto_otherdir; @@ -556,7 +556,7 @@ static int TCPProtoDetect(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, flags, data, data_len); PACKET_PROFILING_APP_END(app_tctx); p->app_update_direction = (uint8_t)app_update_dir; - if (r != 1) { + if (r <= 0) { StreamTcpUpdateAppLayerProgress(ssn, direction, data_len); } if (r == 0) { @@ -576,6 +576,7 @@ static int TCPProtoDetect(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, if (r < 0) { goto parser_error; } + SCReturnInt(r); } else { /* if the ssn is midstream, we may end up with a case where the * start of an HTTP request is missing. We won't detect HTTP based @@ -772,10 +773,14 @@ int AppLayerHandleTCPData(ThreadVars *tv, TcpReassemblyThreadCtx *ra_ctx, Packet if (alproto == ALPROTO_UNKNOWN && (flags & STREAM_START)) { DEBUG_VALIDATE_BUG_ON(FlowChangeProto(f)); /* run protocol detection */ - if (TCPProtoDetect(tv, ra_ctx, app_tctx, p, f, ssn, stream, data, data_len, flags, - app_update_dir) != 0) { + int rd = TCPProtoDetect( + tv, ra_ctx, app_tctx, p, f, ssn, stream, data, data_len, flags, app_update_dir); + if (rd < 0) { goto failure; } + if (rd == 2) { + SCReturnInt(1); + } } else if (alproto != ALPROTO_UNKNOWN && FlowChangeProto(f)) { SCLogDebug("protocol change, old %s", AppProtoToString(f->alproto_orig)); void *alstate_orig = f->alstate; @@ -1061,6 +1066,7 @@ static void AppLayerNamesSetup(void) AppProtoRegisterProtoString(ALPROTO_LDAP, "ldap"); AppProtoRegisterProtoString(ALPROTO_DOH2, "doh2"); AppProtoRegisterProtoString(ALPROTO_MDNS, "mdns"); + AppProtoRegisterProtoString(ALPROTO_SSLPROXY, "sslproxy"); AppProtoRegisterProtoString(ALPROTO_TEMPLATE, "template"); AppProtoRegisterProtoString(ALPROTO_RDP, "rdp"); AppProtoRegisterProtoString(ALPROTO_HTTP2, "http2"); diff --git a/src/flow-bindgen.h b/src/flow-bindgen.h index 753b39ebb11d..e27d3d9bae06 100644 --- a/src/flow-bindgen.h +++ b/src/flow-bindgen.h @@ -25,5 +25,7 @@ void SCFlowGetLastTimeAsParts(const Flow *flow, uint64_t *secs, uint64_t *usecs) uint32_t SCFlowGetFlags(const Flow *flow); uint16_t SCFlowGetSourcePort(const Flow *flow); uint16_t SCFlowGetDestinationPort(const Flow *flow); +int SCFlowSetDecrypted( + Flow *f, uint8_t proto, uint32_t src_ip, uint16_t sp, uint32_t dest_ip, uint16_t dp); #endif /* SURICATA_FLOW_BINDGEN_H */ diff --git a/src/flow-timeout.c b/src/flow-timeout.c index 2e6099ab7fcb..6945c788ba3b 100644 --- a/src/flow-timeout.c +++ b/src/flow-timeout.c @@ -109,16 +109,31 @@ static inline Packet *FlowPseudoPacketSetup( direction ^= ((f->flags & FLOW_DIR_REVERSED) != 0); if (FLOW_IS_IPV4(f)) { - if (direction == 0) { - FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &p->src); - FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &p->dst); - p->sp = f->sp; - p->dp = f->dp; + if ((f->flags & FLOW_IS_DECRYPTED) != 0 && f->translate) { + if (direction == 0) { + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->translate->src, &p->src); + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->translate->dst, &p->dst); + p->sp = f->translate->sp; + p->dp = f->translate->dp; + } else { + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &p->dst); + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &p->src); + p->sp = f->dp; + p->dp = f->sp; + } + } else { - FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &p->dst); - FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &p->src); - p->sp = f->dp; - p->dp = f->sp; + if (direction == 0) { + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &p->src); + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &p->dst); + p->sp = f->sp; + p->dp = f->dp; + } else { + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->src, &p->dst); + FLOW_COPY_IPV4_ADDR_TO_PACKET(&f->dst, &p->src); + p->sp = f->dp; + p->dp = f->sp; + } } /* Check if we have enough room in direct data. We need ipv4 hdr + tcp hdr. diff --git a/src/flow-util.h b/src/flow-util.h index 65ca475e12dd..d7cc3adc3b1a 100644 --- a/src/flow-util.h +++ b/src/flow-util.h @@ -113,6 +113,8 @@ (f)->sgh_toclient = NULL; \ SCGenericVarFree((f)->flowvar); \ (f)->flowvar = NULL; \ + SCFree((f)->translate); \ + (f)->translate = NULL; \ RESET_COUNTERS((f)); \ } while (0) @@ -122,6 +124,7 @@ \ FLOWLOCK_DESTROY((f)); \ SCGenericVarFree((f)->flowvar); \ + SCFree((f)->translate); \ } while (0) /** \brief check if a memory alloc would fit in the memcap diff --git a/src/flow.c b/src/flow.c index 90da16962429..ad7661bd8c60 100644 --- a/src/flow.c +++ b/src/flow.c @@ -519,6 +519,19 @@ void FlowHandlePacketUpdate(Flow *f, Packet *p, ThreadVars *tv, DecodeThreadVars DecodeSetNoPayloadInspectionFlag(p); } + if ((f->flags & FLOW_IS_DECRYPTED) != 0 && f->translate) { + if (FlowGetPacketDirection(f, p) == TOSERVER) { + memcpy(&p->src.address, &f->translate->src.address, 16); + memcpy(&p->dst.address, &f->translate->dst.address, 16); + p->sp = f->translate->sp; + p->dp = f->translate->dp; + } else { + memcpy(&p->src.address, &f->translate->dst.address, 16); + memcpy(&p->dst.address, &f->translate->src.address, 16); + p->sp = f->translate->dp; + p->dp = f->translate->sp; + } + } SCFlowRunUpdateCallbacks(tv, f, p); } @@ -1183,6 +1196,32 @@ void FlowUpdateState(Flow *f, const enum FlowState s) #endif } +/** + * \retval 1 ok + * \retval 0 already set up as decrypte + * \retval -1 error + */ +int SCFlowSetDecrypted( + Flow *f, uint8_t proto, uint32_t src_ip, uint16_t sp, uint32_t dest_ip, uint16_t dp) +{ + if (f->flags & FLOW_IS_DECRYPTED) + return 0; + if (f->translate != NULL) + return 0; + FlowTuple *ft = SCCalloc(1, sizeof(*ft)); + if (ft) { + ft->proto = f->proto; + ft->sp = sp; + ft->dp = dp; + memcpy(&ft->src, &src_ip, sizeof(src_ip)); + memcpy(&ft->dst, &dest_ip, sizeof(dest_ip)); + f->translate = ft; + f->flags |= FLOW_IS_DECRYPTED; + return 1; + } + return -1; +} + /** * \brief Get flow last time as individual values. * diff --git a/src/flow.h b/src/flow.h index ea81e60f7965..98c57cf93955 100644 --- a/src/flow.h +++ b/src/flow.h @@ -61,7 +61,8 @@ typedef struct AppLayerParserState_ AppLayerParserState; /** All packets in this flow should be accepted */ #define FLOW_ACTION_ACCEPT BIT_U32(4) -// vacancy bit 5 +/** decrypted flow */ +#define FLOW_IS_DECRYPTED BIT_U32(5) /** Packet payloads belonging to this flow should not be inspected */ #define FLOW_NOPAYLOAD_INSPECTION BIT_U32(6) @@ -324,6 +325,25 @@ typedef unsigned short FlowStateType; /** Local Thread ID */ typedef uint16_t FlowThreadId; +typedef struct FlowTuple { + FlowAddress src, dst; + union { + Port sp; /**< tcp/udp source port */ + struct { + uint8_t type; /**< icmp type */ + uint8_t code; /**< icmp code */ + } icmp_s; + }; + union { + Port dp; /**< tcp/udp destination port */ + struct { + uint8_t type; /**< icmp type */ + uint8_t code; /**< icmp code */ + } icmp_d; + }; + uint8_t proto; +} __attribute__((__packed__)) FlowTuple; + #include "util-storage.h" /** @@ -489,6 +509,9 @@ typedef struct Flow_ uint64_t todstbytecnt; uint64_t tosrcbytecnt; + // TODO could be using storage? + FlowTuple *translate; + Storage storage[]; } Flow; diff --git a/src/output-json-flow.c b/src/output-json-flow.c index a57160c602b5..cdd575a2090b 100644 --- a/src/output-json-flow.c +++ b/src/output-json-flow.c @@ -53,6 +53,77 @@ #include "flow-storage.h" #include "util-exception-policy.h" +static void AddTranslateTuple(const Flow *f, SCJsonBuilder *jb) +{ + char srcip[46] = { 0 }, dstip[46] = { 0 }; + Port sp, dp; + if ((f->flags & FLOW_DIR_REVERSED) == 0) { + if (FLOW_IS_IPV4(f)) { + PrintInet(AF_INET, (const void *)&(f->translate->src.addr_data32[0]), srcip, + sizeof(srcip)); + PrintInet(AF_INET, (const void *)&(f->translate->dst.addr_data32[0]), dstip, + sizeof(dstip)); + } else if (FLOW_IS_IPV6(f)) { + PrintInet(AF_INET6, (const void *)&(f->translate->src.address), srcip, sizeof(srcip)); + PrintInet(AF_INET6, (const void *)&(f->translate->dst.address), dstip, sizeof(dstip)); + } + sp = f->translate->sp; + dp = f->translate->dp; + } else { + if (FLOW_IS_IPV4(f)) { + PrintInet(AF_INET, (const void *)&(f->translate->dst.addr_data32[0]), srcip, + sizeof(srcip)); + PrintInet(AF_INET, (const void *)&(f->translate->src.addr_data32[0]), dstip, + sizeof(dstip)); + } else if (FLOW_IS_IPV6(f)) { + PrintInet(AF_INET6, (const void *)&(f->translate->dst.address), srcip, sizeof(srcip)); + PrintInet(AF_INET6, (const void *)&(f->translate->src.address), dstip, sizeof(dstip)); + } + sp = f->translate->dp; + dp = f->translate->sp; + } + SCJbSetString(jb, "src_ip", srcip); + switch (f->proto) { + case IPPROTO_ICMP: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + SCJbSetUint(jb, "src_port", sp); + break; + } + SCJbSetString(jb, "dest_ip", dstip); + switch (f->proto) { + case IPPROTO_ICMP: + break; + case IPPROTO_UDP: + case IPPROTO_TCP: + case IPPROTO_SCTP: + SCJbSetUint(jb, "dest_port", dp); + break; + } + + if (SCProtoNameValid(f->proto)) { + SCJbSetString(jb, "proto", known_proto[f->proto]); + } else { + char proto[4]; + snprintf(proto, sizeof(proto), "%" PRIu8 "", f->proto); + SCJbSetString(jb, "proto", proto); + } + + switch (f->proto) { + case IPPROTO_ICMP: + case IPPROTO_ICMPV6: + SCJbSetUint(jb, "icmp_type", f->icmp_s.type); + SCJbSetUint(jb, "icmp_code", f->icmp_s.code); + if (f->tosrcpktcnt) { + SCJbSetUint(jb, "response_icmp_type", f->icmp_d.type); + SCJbSetUint(jb, "response_icmp_code", f->icmp_d.code); + } + break; + } +} + static SCJsonBuilder *CreateEveHeaderFromFlow(const Flow *f) { char timebuf[64]; @@ -121,6 +192,10 @@ static SCJsonBuilder *CreateEveHeaderFromFlow(const Flow *f) SCJbClose(jb); } + if (f->translate) { + AddTranslateTuple(f, jb); + SCJbOpenObject(jb, "orig"); + } /* tuple */ SCJbSetString(jb, "src_ip", srcip); switch(f->proto) { @@ -172,6 +247,8 @@ static SCJsonBuilder *CreateEveHeaderFromFlow(const Flow *f) SCJbSetUint(jb, "spi", f->esp.spi); break; } + if (f->translate) + SCJbClose(jb); return jb; } diff --git a/src/stream-tcp-reassemble.c b/src/stream-tcp-reassemble.c index 6ab546f28c05..fa768935853c 100644 --- a/src/stream-tcp-reassemble.c +++ b/src/stream-tcp-reassemble.c @@ -1364,13 +1364,21 @@ static int ReassembleUpdateAppLayer(ThreadVars *tv, TcpReassemblyThreadCtx *ra_c } (*stream)->data_required = 0; - SCLogDebug("parser"); + const uint64_t old_app_progress = STREAM_APP_PROGRESS(*stream); /* update the app-layer */ - (void)AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream, (uint8_t *)mydata, + int r = AppLayerHandleTCPData(tv, ra_ctx, p, p->flow, ssn, stream, (uint8_t *)mydata, mydata_len, flags, app_update_dir); AppLayerProfilingStore(ra_ctx->app_tctx, p); AppLayerFrameDump(p->flow); uint64_t new_app_progress = STREAM_APP_PROGRESS(*stream); + /* if we consumed some, but not all, we do another round as this is + * due to partial ok support */ + if (r == 1 && old_app_progress != new_app_progress && + old_app_progress + mydata_len > new_app_progress && !(flags & STREAM_DEPTH) && + FlowChangeProto(p->flow)) { + app_progress = new_app_progress; + continue; + } if (new_app_progress == app_progress || FlowChangeProto(p->flow)) break; app_progress = new_app_progress;