From eacb80066c041d33b44721f606764a34d4ad22b5 Mon Sep 17 00:00:00 2001 From: Juliana Fajardini Date: Fri, 5 Feb 2021 10:20:10 +0000 Subject: [PATCH] PostgreSQL: initial support (MVP) PostgreSQL: add SSL handshake support -- applayerpgsql/parser: add parsers for request and response messages -- applayerpgsql/pgsql: add unittests for parse_response -- this parser does NOT yet handle what happens after the handshake --- rust/src/applayerpgsql/logger.rs | 36 ++ rust/src/applayerpgsql/mod.rs | 20 + rust/src/applayerpgsql/parser.rs | 212 +++++++++ rust/src/applayerpgsql/pgsql.rs | 717 +++++++++++++++++++++++++++++++ rust/src/lib.rs | 1 + src/Makefile.am | 2 + src/app-layer-detect-proto.c | 4 + src/app-layer-parser.c | 2 + src/app-layer-pgsql.c | 65 +++ src/app-layer-pgsql.h | 30 ++ src/app-layer-protos.c | 5 + src/app-layer-protos.h | 1 + src/output-json-pgsql.c | 186 ++++++++ src/output-json-pgsql.h | 29 ++ src/output.c | 3 + src/suricata-common.h | 1 + src/util-profiling.c | 1 + suricata.yaml.in | 3 + 18 files changed, 1318 insertions(+) create mode 100644 rust/src/applayerpgsql/logger.rs create mode 100644 rust/src/applayerpgsql/mod.rs create mode 100644 rust/src/applayerpgsql/parser.rs create mode 100644 rust/src/applayerpgsql/pgsql.rs create mode 100644 src/app-layer-pgsql.c create mode 100644 src/app-layer-pgsql.h create mode 100644 src/output-json-pgsql.c create mode 100644 src/output-json-pgsql.h diff --git a/rust/src/applayerpgsql/logger.rs b/rust/src/applayerpgsql/logger.rs new file mode 100644 index 000000000000..5e86bc9ec753 --- /dev/null +++ b/rust/src/applayerpgsql/logger.rs @@ -0,0 +1,36 @@ +/* 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 std; +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use super::pgsql::PGSQLTransaction; + +fn log_pgsql(tx: &PGSQLTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + if let Some(ref request) = tx.request { + js.set_string("request", &request.to_string())?; + } + if let Some(ref response) = tx.response { + js.set_string("response", &response.to_string())?; + } + Ok(()) +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_logger_log(tx: *mut std::os::raw::c_void, js: &mut JsonBuilder) -> bool { + let tx = cast_pointer!(tx, PGSQLTransaction); + log_pgsql(tx, js).is_ok() +} diff --git a/rust/src/applayerpgsql/mod.rs b/rust/src/applayerpgsql/mod.rs new file mode 100644 index 000000000000..15df5470461e --- /dev/null +++ b/rust/src/applayerpgsql/mod.rs @@ -0,0 +1,20 @@ +/* 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. + */ + +pub mod pgsql; +pub mod logger; +mod parser; diff --git a/rust/src/applayerpgsql/parser.rs b/rust/src/applayerpgsql/parser.rs new file mode 100644 index 000000000000..96aefc022840 --- /dev/null +++ b/rust/src/applayerpgsql/parser.rs @@ -0,0 +1,212 @@ +/* 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::{fmt, self, u16, u8}; +use nom::{number::streaming::{be_u16, be_u32}}; + +// Dummy protocol major version, used for SSL, GSSAPI or Cancellation requests +pub const PGSQL_DUMMY_PROTO_MAJOR: u16 = 1234; + +// Dummy protocol minor version, used for SSL encryption requests +pub const PGSQL_DUMMY_PROTO_MINOR_SSL: u16 = 5679; + +pub const PGSQL_FLAG_SSL_NEGATIVE_RESPONSE: u8 = 'N' as u8; +pub const PGSQL_FLAG_SSL_POSITIVE_RESPONSE: u8 = 'S' as u8; + +#[derive(Debug, PartialEq)] +pub enum StartupMessageType { + StartupMessage = 0, + SslRequest = 1, + GssApiRequest = 2, +} + +impl fmt::Display for StartupMessageType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + StartupMessageType::StartupMessage => f.write_str("Startup Message"), + StartupMessageType::SslRequest => f.write_str("SSL Request"), + StartupMessageType::GssApiRequest => f.write_str("GSSAPI Request"), + } + } +} + +#[derive(Debug, PartialEq)] +pub struct PgsqlGenericStartupMessage{ + message_type: StartupMessageType, + length: u32, + proto_major: u16, + proto_minor: u16, +} + +impl fmt::Display for PgsqlGenericStartupMessage { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +named!(pub parse_ssl_request, + do_parse!( + len: verify!(be_u32, |&x| x == 8 ) + >> proto_major: verify!(be_u16, |&x| x == PGSQL_DUMMY_PROTO_MAJOR ) + >> proto_minor: verify!(be_u16, |&x| x == PGSQL_DUMMY_PROTO_MINOR_SSL ) + >> (PgsqlGenericStartupMessage { + message_type: StartupMessageType::SslRequest, + length: len, + proto_major: proto_major, + proto_minor: proto_minor, + }) +)); + +#[derive(Debug, PartialEq)] +pub struct PgsqlSslResponse { + message_type: u8, +} + +impl fmt::Display for PgsqlSslResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +named!(pub parse_ssl_response, + do_parse!( + identifier: alt!(char!('N') | char!('S')) + >> (PgsqlSslResponse{ + message_type: identifier as u8, + }) + )); + +// TODO will I use this? Maybe not... +fn parse_len(input: &str) -> Result { + input.parse::() +} + +// Parse a request message +// +// For now, only parses SSL encryption requests +named!(pub parse_request_message, + do_parse!( + res: parse_ssl_request + >> (res) +)); + +// Parse a request message +// +// For now, only parses SSL encryption responses +named!(pub parse_response_message, + do_parse!( + message: parse_ssl_response + >> (message) +)); + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_parse_pgsql_ssl_request() { + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + let ok_res = PgsqlGenericStartupMessage { + message_type: StartupMessageType::SslRequest, + length: 8, + proto_major: PGSQL_DUMMY_PROTO_MAJOR, + proto_minor: PGSQL_DUMMY_PROTO_MINOR_SSL, + }; + let (_remainder, res) = parse_ssl_request(&buf).unwrap(); + assert_eq!(res, ok_res); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x30]; + let res = parse_ssl_request(&buf); + assert!(res.is_err()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x01, 0x04, 0xd2, 0x16, 0x2f]; + let res = parse_ssl_request(&buf); + assert!(res.is_err()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16]; + let res = parse_ssl_request(&buf); + assert!(res.is_err()); + } + + #[test] + fn test_parse_pgsql_ssl_response() { + // 'S' + let input: &[u8] = &[0x53]; + + let ok_res = PgsqlSslResponse { + message_type: PGSQL_FLAG_SSL_POSITIVE_RESPONSE, + }; + + let (_remainder, result) = parse_ssl_response(&input).unwrap(); + assert_eq!(result, ok_res); + + // 'N' + let input: &[u8] = &[0x4e]; + + let ok_res = PgsqlSslResponse { + message_type: PGSQL_FLAG_SSL_NEGATIVE_RESPONSE, + }; + + let (_remainder, result) = parse_ssl_response(&input).unwrap(); + assert_eq!(result, ok_res); + + // 'O' -- invalid option + let input: &[u8] = &[0x4f]; + let result = parse_ssl_response(&input); + assert!(result.is_err()); + } + + #[test] + fn test_parse_request_message() { + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + let res = parse_request_message(&buf); + assert!(res.is_ok()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x07, 0x04, 0xd2, 0x16, 0x2f]; + let res = parse_request_message(&buf); + assert!(res.is_err()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x07, 0x04, 0xd2, 0x16, 0x2f]; + let res = parse_request_message(&buf); + assert!(res.is_err()); + } + + #[test] + fn test_parse_response_message() { + // 'S' + let input: &[u8] = &[0x53]; + + let ok_res = PgsqlSslResponse { + message_type: PGSQL_FLAG_SSL_POSITIVE_RESPONSE, + }; + + let (_remainder, result) = parse_response_message(&input).unwrap(); + assert_eq!(result, ok_res); + + // 'N' + let input: &[u8] = &[0x4e]; + + let ok_res = PgsqlSslResponse { + message_type: PGSQL_FLAG_SSL_NEGATIVE_RESPONSE, + }; + + let (_remainder, result) = parse_response_message(&input).unwrap(); + assert_eq!(result, ok_res); + } +} diff --git a/rust/src/applayerpgsql/pgsql.rs b/rust/src/applayerpgsql/pgsql.rs new file mode 100644 index 000000000000..8e36ed1e2f88 --- /dev/null +++ b/rust/src/applayerpgsql/pgsql.rs @@ -0,0 +1,717 @@ +/* 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::{self, ALPROTO_UNKNOWN, AppProto, Flow, IPPROTO_TCP}; +use std::mem::transmute; +use crate::applayer::{self, *}; +use std::ffi::CString; +use nom; +use parser::{PgsqlGenericStartupMessage, PgsqlSslResponse}; +use std::fmt; +use super::parser; +use nom::error::{ErrorKind, ParseError}; + +static mut ALPROTO_POSTGRESQL: AppProto = ALPROTO_UNKNOWN; + +#[derive(Debug)] +pub enum PGSQLError { + MalformedData, + NotEnoughData, // not sure about this + StreamTooSmall, // not sure about this + UnknownError, // TODO should this exist? + NomError(ErrorKind), +} + +impl ParseError for PGSQLError { + fn from_error_kind(_input: I, kind: ErrorKind) -> Self { + PGSQLError::NomError(kind) + } + + fn append(_input: I, kind: ErrorKind, _other: Self) -> Self { + PGSQLError::NomError(kind) + } +} + +impl fmt::Display for PGSQLError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + PGSQLError::MalformedData => f.write_str("Malformed data"), + PGSQLError::NotEnoughData => f.write_str("Not enough data"), + PGSQLError::StreamTooSmall => f.write_str("Stream is too small"), + PGSQLError::UnknownError => f.write_str("Unknown error"), + PGSQLError::NomError(ref inner) => f.write_str(inner.description()), + } + } +} + +pub struct PGSQLTransaction { + tx_id: u64, + pub request: Option, // TODO implement a struct generic enough to fit all requests here + pub response: Option, // TODO implement a struct generic enough to fit all responses here + + de_state: Option<*mut core::DetectEngineState>, + events: *mut core::AppLayerDecoderEvents, + tx_data: AppLayerTxData, +} + +impl PGSQLTransaction { + pub fn new() -> PGSQLTransaction { + PGSQLTransaction { + tx_id: 0, + request: None, + response: None, + de_state: None, + events: std::ptr::null_mut(), + tx_data: AppLayerTxData::new(), + } + } + + pub fn free(&mut self) { + if self.events != std::ptr::null_mut() { + core::sc_app_layer_decoder_events_free_events(&mut self.events); + } + if let Some(state) = self.de_state { + core::sc_detect_engine_state_free(state); + } + } +} + +impl Drop for PGSQLTransaction { + fn drop(&mut self) { + self.free(); + } +} + +pub struct PGSQLState { + tx_id: u64, + transactions: Vec, + request_gap: bool, + response_gap: bool, +} + +impl PGSQLState { + pub fn new() -> Self { + Self { + tx_id: 0, + transactions: Vec::new(), + request_gap: false, + response_gap: false, + } + } + + // 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<&PGSQLTransaction> { + for tx in &mut self.transactions { + if tx.tx_id == tx_id + 1 { + return Some(tx); + } + } + return None; + } + + fn new_tx(&mut self) -> PGSQLTransaction { + let mut tx = PGSQLTransaction::new(); + self.tx_id += 1; + tx.tx_id = self.tx_id; + return tx; + } + + fn find_request(&mut self) -> Option<&mut PGSQLTransaction> { + for tx in &mut self.transactions { + if tx.response.is_none() { + return Some(tx); + } + } + None + } + + fn parse_request(&mut self, input: &[u8]) -> AppLayerResult { + // We're not interested in empty requests. + // TODO: do I have to change this ok() to something else? + if input.len() == 0 { + return AppLayerResult::ok(); + } + + // If there was gap, check we can sync up again. + if self.request_gap { + SCLogNotice!("Pgsql parser detected a gap."); + if probe_request(input).is_err() { + // The parser now needs to decide what to do as we are not in sync. + // For this pgsql, we'll just try again next time. + // TODO to implement + 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.len() > 0 { + match parser::parse_request_message(start) { + Ok((rem, request)) => { + start = rem; + + SCLogNotice!("Request is: {}", &request); + let mut tx = self.new_tx(); + tx.request = Some(request); + self.transactions.push(tx); + }, + Err(nom::Err::Incomplete(needed)) => { + // Not enough data. This parser doesn't give us a good indication yet + // 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. + // TODO print the incomplete + + if let nom::Needed::Size(n) = needed { + SCLogDebug!("Not enough data for request message {:?}", needed); + let need = n; + let consumed = input.len() - start.len(); + return AppLayerResult::incomplete(consumed as u32, need as u32); + } + return AppLayerResult::err(); + }, + 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.len() == 0 { + return AppLayerResult::ok(); + } + + SCLogNotice!("Parsing response..."); + let mut start = input; + while start.len() > 0 { + match parser::parse_response_message(start) { + Ok((rem, response)) => { + start = rem; + + match self.find_request() { + Some(tx) => { + tx.response = Some(response); + SCLogNotice!("Found response for request:"); + SCLogNotice!("- Request: {:?}", tx.request); + SCLogNotice!("- Response: {:?}", tx.response); + } + None => {} + } + } + 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 tx_iterator( + &mut self, + min_tx_id: u64, + state: &mut u64, + ) -> Option<(&PGSQLTransaction, 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 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 request. +fn probe_request(input: &[u8]) -> nom::IResult<&[u8], PgsqlGenericStartupMessage> { + SCLogNotice!("Probing request..."); + parser::parse_ssl_request(&input) +} + +/// Probe for a valid response +fn probe_response(input: &[u8]) -> nom::IResult<&[u8], PgsqlSslResponse> { + SCLogNotice!("Probing response..."); + parser::parse_ssl_response(&input) +} + +// C exports. + +export_tx_get_detect_state!( + rs_pgsql_tx_get_detect_state, + PGSQLTransaction +); +export_tx_set_detect_state!( + rs_pgsql_tx_set_detect_state, + PGSQLTransaction +); + +/// C entry point for a probing parser. +#[no_mangle] +pub extern "C" fn rs_pgsql_probing_parser_ts( + _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 != std::ptr::null_mut() { + let slice = build_slice!(input, input_len as usize); + if probe_request(slice).is_ok() { + SCLogNotice!("Probing request: ok"); + return unsafe { ALPROTO_POSTGRESQL }; + } + } + return ALPROTO_UNKNOWN; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_probing_parser_tc( + _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 != std::ptr::null_mut() { + let slice = build_slice!(input, input_len as usize); + if probe_response(slice).is_ok() { + SCLogNotice!("Probing response: ok"); + return unsafe { ALPROTO_POSTGRESQL }; + } + } + 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 unsafe { transmute(boxed) }; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_free(state: *mut std::os::raw::c_void) { + // Just unbox... + let _drop: Box = unsafe { transmute(state) }; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_tx_free( + state: *mut std::os::raw::c_void, + tx_id: u64, +) { + let state = cast_pointer!(state, PGSQLState); + state.free_tx(tx_id); +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_parse_request( + _flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + input: *const u8, + input_len: u32, + _data: *const std::os::raw::c_void, + _flags: u8, +) -> AppLayerResult { + let eof = unsafe { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { + true + } else { + false + } + }; + + if eof { + // If needed, handled EOF, or pass it into the parser. + return AppLayerResult::ok(); + } + + let state = cast_pointer!(state, PGSQLState); + + if input == std::ptr::null_mut() && input_len > 0 { + // 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(input_len); + AppLayerResult::ok() + } else { + let buf = build_slice!(input, input_len as usize); + state.parse_request(buf).into() // TODO What's the difference between using this or not? + } +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_parse_response( + _flow: *const Flow, + state: *mut std::os::raw::c_void, + pstate: *mut std::os::raw::c_void, + input: *const u8, + input_len: u32, + _data: *const std::os::raw::c_void, + _flags: u8, +) -> AppLayerResult { + let _eof = unsafe { + if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF) > 0 { + true + } else { + false + } + }; + let state = cast_pointer!(state, PGSQLState); + + if input == std::ptr::null_mut() && input_len > 0 { + // 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(input_len); + AppLayerResult::ok() + } else { + let buf = build_slice!(input, input_len as usize); + state.parse_response(buf).into() + } +} + +#[no_mangle] +pub 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 = cast_pointer!(state, PGSQLState); + match state.get_tx(tx_id) { + Some(tx) => { + return unsafe { transmute(tx) }; + } + 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 = cast_pointer!(state, PGSQLState); + return state.tx_id; +} + +#[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 { + let tx = cast_pointer!(tx, PGSQLTransaction); + + // Transaction is done if we have a response. + if tx.response.is_some() { + return 1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_get_events( + tx: *mut std::os::raw::c_void +) -> *mut core::AppLayerDecoderEvents { + let tx = cast_pointer!(tx, PGSQLTransaction); + return tx.events; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_state_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 { + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_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 core::AppLayerEventType +) -> i8 { + return -1; +} + +#[no_mangle] +pub extern "C" fn rs_pgsql_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, PGSQLState); + match state.tx_iterator(min_tx_id, istate) { + Some((tx, out_tx_id, has_next)) => { + let c_tx = unsafe { transmute(tx) }; + let ires = applayer::AppLayerGetTxIterTuple::with_values( + c_tx, + out_tx_id, + has_next, + ); + return ires; + } + None => { + return applayer::AppLayerGetTxIterTuple::not_found(); + } + } +} + +// 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 extern "C" fn rs_pgsql_get_request_buffer( +// tx: *mut std::os::raw::c_void, +// buf: *mut *const u8, +// len: *mut u32, +// ) -> u8 +// { //TODO I'm not sure how would I use this, yet. Should I pass the message length field? +// let tx = cast_pointer!(tx, PGSQLTransaction); +// if let Some(ref request) = tx.request { +// if request.len() > 0 { +// unsafe { +// *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 extern "C" fn rs_pgsql_get_response_buffer( +// tx: *mut std::os::raw::c_void, +// buf: *mut *const u8, +// len: *mut u32, +// ) -> u8 +// { //TODO will I need this? How can I adapt it? +// let tx = cast_pointer!(tx, PGSQLTransaction); +// if let Some(ref response) = tx.response { +// if response.len() > 0 { +// unsafe { +// *len = response.len() as u32; +// *buf = response.as_ptr(); +// } +// return 1; +// } +// } +// return 0; +// } + +export_tx_data_get!(rs_pgsql_get_tx_data, PGSQLTransaction); + +// Parser name as a C style string. +const PARSER_NAME: &'static [u8] = b"postgresql\0"; + +#[no_mangle] +pub unsafe extern "C" fn rs_pgsql_register_parser() { + let default_port = CString::new("[5432]").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_pgsql_probing_parser_ts), + probe_tc: Some(rs_pgsql_probing_parser_tc), + min_depth: 0, + max_depth: 16, //TODO find out what should be the max depth + 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: 1, + tx_comp_st_tc: 1, + tx_get_progress: rs_pgsql_tx_get_alstate_progress, + get_de_state: rs_pgsql_tx_get_detect_state, + set_de_state: rs_pgsql_tx_set_detect_state, + get_events: Some(rs_pgsql_state_get_events), + get_eventinfo: Some(rs_pgsql_state_get_event_info), + get_eventinfo_byid : Some(rs_pgsql_state_get_event_info_by_id), + localstorage_new: None, + localstorage_free: None, + get_files: None, + get_tx_iterator: Some(rs_pgsql_state_get_tx_iterator), + get_tx_data: rs_pgsql_get_tx_data, + apply_tx_config: None, + flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, + truncate: 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_POSTGRESQL = alproto; + if AppLayerParserConfParserEnabled( + ip_proto_str.as_ptr(), + parser.name, + ) != 0 + { + let _ = AppLayerRegisterParser(&parser, alproto); + } + SCLogNotice!("Rust PostgreSQL parser registered."); + } else { + SCLogNotice!("Protocol detector and parser disabled for PostgreSQL."); + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_probe_ssl_request() { + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + let res = probe_request(&buf); + assert!(res.is_ok()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x30]; + let res = probe_request(&buf); + assert!(res.is_err()); + + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2]; + let res = probe_request(&buf); + assert!(res.is_err()); + println!("{}", res.unwrap_err()); + } + + #[test] + fn test_probe_ssl_response() { + // 'S' + let buf: &[u8] = &[0x53]; + let res = probe_response(&buf); + assert!(res.is_ok()); + + // 'N' + let buf: &[u8] = &[0x4e]; + let res = probe_response(&buf); + assert!(res.is_ok()); + + let buf: &[u8] = &[0x30]; + let res = probe_response(&buf); + assert!(res.is_err()); + + let buf: &[u8] = &[0x30, 0x31]; + let res = probe_response(&buf); + assert!(res.is_err()); + } + + #[test] + fn test_incomplete_request() { + let mut state = PGSQLState::new(); + let buf: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x16, 0x2f]; + + // If we have nothing, we shouldn't be able to parse anything + 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: 4}); + + let r = state.parse_request(&buf[0..2]); + assert_eq!(r, AppLayerResult{ status: 1, consumed: 0, needed: 4}); + + // Ok, length (4 bytes) parsed. Expects 2 bytes for proto version major + let r = state.parse_request(&buf[0..5]); + assert_eq!(r, AppLayerResult{ status: 1, consumed: 0, needed: 2}); + + // Now, missing 2 bytes for proto version min + let r = state.parse_request(&buf[0..7]); + assert_eq!(r, AppLayerResult{ status: 1, consumed: 0, needed: 2}); + } + + #[test] + fn test_ssl_response() { + let mut state = PGSQLState::new(); + // 'S' + let buf: &[u8] = &[0x53]; + let r = state.parse_response(&buf); + assert_eq!(r, AppLayerResult{ status: 0, consumed: 0, needed: 0}); + + // 'N' + let buf: &[u8] = &[0x4e]; + let r = state.parse_response(&buf); + assert_eq!(r, AppLayerResult{ status: 0, consumed: 0, needed: 0}); + + // this is an invalid character here + let buf: &[u8] = &[0x30]; + let r = state.parse_response(&buf); + println!("AppLayerResult is: {:?}", r); + assert_eq!(r, AppLayerResult{ status: -1, consumed: 0, needed: 0}); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4d054abad1d3..e1275be88218 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -76,6 +76,7 @@ pub mod dhcp; pub mod sip; pub mod rfb; pub mod mqtt; +pub mod applayerpgsql; pub mod applayertemplate; pub mod rdp; pub mod x509; diff --git a/src/Makefile.am b/src/Makefile.am index 137f3101082e..3f4d7ce0b3cf 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ app-layer-ikev2.c app-layer-ikev2.h \ app-layer-krb5.c app-layer-krb5.h \ app-layer-rfb.c app-layer-rfb.h \ app-layer-mqtt.c app-layer-mqtt.h \ +app-layer-pgsql.c app-layer-pgsql.h \ app-layer-template.c app-layer-template.h \ app-layer-template-rust.c app-layer-template-rust.h \ app-layer-rdp.c app-layer-rdp.h \ @@ -393,6 +394,7 @@ output-json-dhcp.c output-json-dhcp.h \ output-json-snmp.c output-json-snmp.h \ output-json-rfb.c output-json-rfb.h \ output-json-mqtt.c output-json-mqtt.h \ +output-json-pgsql.c output-json-pgsql.h \ output-json-template.c output-json-template.h \ output-json-template-rust.c output-json-template-rust.h \ output-json-rdp.c output-json-rdp.h \ diff --git a/src/app-layer-detect-proto.c b/src/app-layer-detect-proto.c index c123e12e3f58..e6f98bde85a4 100644 --- a/src/app-layer-detect-proto.c +++ b/src/app-layer-detect-proto.c @@ -915,6 +915,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_MQTT) printf(" alproto: ALPROTO_MQTT\n"); + else if (pp_pe->alproto == ALPROTO_POSTGRESQL) + printf(" alproto: ALPROTO_POSTGRESQL\n"); else if (pp_pe->alproto == ALPROTO_TEMPLATE) printf(" alproto: ALPROTO_TEMPLATE\n"); else if (pp_pe->alproto == ALPROTO_DNP3) @@ -992,6 +994,8 @@ static void AppLayerProtoDetectPrintProbingParsers(AppLayerProtoDetectProbingPar printf(" alproto: ALPROTO_RFB\n"); else if (pp_pe->alproto == ALPROTO_MQTT) printf(" alproto: ALPROTO_MQTT\n"); + else if (pp_pe->alproto == ALPROTO_POSTGRESQL) + printf(" alproto: ALPROTO_POSTGRESQL\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-parser.c b/src/app-layer-parser.c index f94c81ddebb1..2af4e3ac3a97 100644 --- a/src/app-layer-parser.c +++ b/src/app-layer-parser.c @@ -67,6 +67,7 @@ #include "app-layer-sip.h" #include "app-layer-rfb.h" #include "app-layer-mqtt.h" +#include "app-layer-pgsql.h" #include "app-layer-template.h" #include "app-layer-template-rust.h" #include "app-layer-rdp.h" @@ -1629,6 +1630,7 @@ void AppLayerParserRegisterProtocolParsers(void) RegisterTemplateRustParsers(); RegisterRFBParsers(); RegisterMQTTParsers(); + RegisterPGSQLParsers(); RegisterTemplateParsers(); RegisterRdpParsers(); RegisterHTTP2Parsers(); diff --git a/src/app-layer-pgsql.c b/src/app-layer-pgsql.c new file mode 100644 index 000000000000..70c82be0b1db --- /dev/null +++ b/src/app-layer-pgsql.c @@ -0,0 +1,65 @@ +/* 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. + */ + +/* + * TODO: Update \author in this file and app-layer-pgsqlrust.h. + * TODO: Implement your app-layer logic with unit tests. + * TODO: Remove SCLogNotice statements or convert to debug. + */ + +/** + * \file + * + * \author FirstName LastName + * + * PGSQL application layer detector and parser for learning and + * pgsqlrust pruposes. + * + * This pgsqlrust implements a simple application layer for something + * like the echo protocol running on port 7. + */ + +#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-pgsql.h" +#include "rust.h" + +void RegisterPGSQLParsers(void) +{ + SCLogNotice("Registering Rust PostgreSQL parser."); + rs_pgsql_register_parser(); +#ifdef UNITTESTS + AppLayerParserRegisterProtocolUnittests( + IPPROTO_TCP, ALPROTO_POSTGRESQL, PGSQLParserRegisterTests); +#endif +} + +#ifdef UNITTESTS +#endif + +void PGSQLParserRegisterTests(void) +{ +#ifdef UNITTESTS +#endif +} diff --git a/src/app-layer-pgsql.h b/src/app-layer-pgsql.h new file mode 100644 index 000000000000..2dcb2c99a2b4 --- /dev/null +++ b/src/app-layer-pgsql.h @@ -0,0 +1,30 @@ +/* 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. + */ + +/** + * \file + * + * \author FirstName LastName + */ + +#ifndef __APP_LAYER_PGSQL_H__ +#define __APP_LAYER_PGSQL_H__ + +void RegisterPGSQLParsers(void); +void PGSQLParserRegisterTests(void); + +#endif /* __APP_LAYER_PGSQL_H__ */ diff --git a/src/app-layer-protos.c b/src/app-layer-protos.c index ecb00684ae1d..8519981e4b57 100644 --- a/src/app-layer-protos.c +++ b/src/app-layer-protos.c @@ -108,6 +108,9 @@ const char *AppProtoToString(AppProto alproto) case ALPROTO_MQTT: proto_name = "mqtt"; break; + case ALPROTO_POSTGRESQL: + proto_name = "postgresql"; + break; case ALPROTO_TEMPLATE: proto_name = "template"; break; @@ -163,6 +166,8 @@ AppProto StringToAppProto(const char *proto_name) if (strcmp(proto_name,"sip")==0) return ALPROTO_SIP; if (strcmp(proto_name,"rfb")==0) return ALPROTO_RFB; if (strcmp(proto_name,"mqtt")==0) return ALPROTO_MQTT; + if (strcmp(proto_name, "postgresql") == 0) + return ALPROTO_POSTGRESQL; if (strcmp(proto_name,"template")==0) return ALPROTO_TEMPLATE; if (strcmp(proto_name,"template-rust")==0) return ALPROTO_TEMPLATE_RUST; if (strcmp(proto_name,"rdp")==0) return ALPROTO_RDP; diff --git a/src/app-layer-protos.h b/src/app-layer-protos.h index 278c3e4389b0..0e0988460f31 100644 --- a/src/app-layer-protos.h +++ b/src/app-layer-protos.h @@ -53,6 +53,7 @@ enum AppProtoEnum { ALPROTO_SIP, ALPROTO_RFB, ALPROTO_MQTT, + ALPROTO_POSTGRESQL, ALPROTO_TEMPLATE, ALPROTO_TEMPLATE_RUST, ALPROTO_RDP, diff --git a/src/output-json-pgsql.c b/src/output-json-pgsql.c new file mode 100644 index 000000000000..90eed479ba57 --- /dev/null +++ b/src/output-json-pgsql.c @@ -0,0 +1,186 @@ +/* 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. + */ + +/* + * TODO: Update \author in this file and in output-json-pgsql.h. + * TODO: Remove SCLogNotice statements, or convert to debug. + * TODO: Implement your app-layers logging. + */ + +/** + * \file + * + * \author FirstName LastName + * + * Implement JSON/eve logging app-layer PGSQL. + */ + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "pkt-var.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-threads.h" + +#include "util-unittest.h" +#include "util-buffer.h" +#include "util-debug.h" +#include "util-byte.h" + +#include "output.h" +#include "output-json.h" + +#include "app-layer.h" +#include "app-layer-parser.h" + +#include "app-layer-pgsql.h" +#include "output-json-pgsql.h" +#include "rust.h" + +typedef struct LogPGSQLFileCtx_ { + LogFileCtx *file_ctx; + uint32_t flags; +} LogPGSQLFileCtx; + +typedef struct LogPGSQLLogThread_ { + LogPGSQLFileCtx *pgsqllog_ctx; + LogFileCtx *file_ctx; + MemBuffer *buffer; +} LogPGSQLLogThread; + +static int JsonPGSQLLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, + void *tx, uint64_t tx_id) +{ + SCLogNotice("JsonPGSQLLogger"); + LogPGSQLLogThread *thread = thread_data; + + JsonBuilder *js = CreateEveHeader(p, LOG_DIR_PACKET, "postgresql", NULL); + if (unlikely(js == NULL)) { + return TM_ECODE_FAILED; + } + + jb_open_object(js, "postgresql"); + if (!rs_pgsql_logger_log(tx, js)) { + goto error; + } + jb_close(js); + + MemBufferReset(thread->buffer); + OutputJsonBuilderBuffer(js, thread->file_ctx, &thread->buffer); + jb_free(js); + + return TM_ECODE_OK; + +error: + jb_free(js); + return TM_ECODE_FAILED; +} + +static void OutputPGSQLLogDeInitCtxSub(OutputCtx *output_ctx) +{ + LogPGSQLFileCtx *pgsqllog_ctx = (LogPGSQLFileCtx *)output_ctx->data; + SCFree(pgsqllog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputPGSQLLogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ajt = parent_ctx->data; + + LogPGSQLFileCtx *pgsqllog_ctx = SCCalloc(1, sizeof(*pgsqllog_ctx)); + if (unlikely(pgsqllog_ctx == NULL)) { + return result; + } + pgsqllog_ctx->file_ctx = ajt->file_ctx; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(*output_ctx)); + if (unlikely(output_ctx == NULL)) { + SCFree(pgsqllog_ctx); + return result; + } + output_ctx->data = pgsqllog_ctx; + output_ctx->DeInit = OutputPGSQLLogDeInitCtxSub; + + SCLogNotice("PostgreSQL log sub-module initialized."); + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_POSTGRESQL); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonPGSQLLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogPGSQLLogThread *thread = SCCalloc(1, sizeof(*thread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogPGSQL. \"initdata\" is NULL."); + goto error_exit; + } + + thread->buffer = MemBufferCreateNew(JSON_OUTPUT_BUFFER_SIZE); + if (unlikely(thread->buffer == NULL)) { + goto error_exit; + } + + thread->pgsqllog_ctx = ((OutputCtx *)initdata)->data; + thread->file_ctx = LogFileEnsureExists(thread->pgsqllog_ctx->file_ctx, t->id); + if (!thread->file_ctx) { + goto error_exit; + } + *data = (void *)thread; + + return TM_ECODE_OK; + +error_exit: + if (thread->buffer != NULL) { + MemBufferFree(thread->buffer); + } + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonPGSQLLogThreadDeinit(ThreadVars *t, void *data) +{ + LogPGSQLLogThread *thread = (LogPGSQLLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + if (thread->buffer != NULL) { + MemBufferFree(thread->buffer); + } + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonPGSQLLogRegister(void) +{ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_POSTGRESQL, "eve-log", "JsonPGSQLLog", + "eve-log.postgresql", OutputPGSQLLogInitSub, ALPROTO_POSTGRESQL, JsonPGSQLLogger, + JsonPGSQLLogThreadInit, JsonPGSQLLogThreadDeinit, NULL); + + SCLogNotice("PostgreSQL JSON logger registered."); +} diff --git a/src/output-json-pgsql.h b/src/output-json-pgsql.h new file mode 100644 index 000000000000..c1643d1436ac --- /dev/null +++ b/src/output-json-pgsql.h @@ -0,0 +1,29 @@ +/* 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. + */ + +/** + * \file + * + * \author FirstName LastName + */ + +#ifndef __OUTPUT_JSON_PGSQL_H__ +#define __OUTPUT_JSON_PGSQL_H__ + +void JsonPGSQLLogRegister(void); + +#endif /* __OUTPUT_JSON_PGSQL_H__ */ diff --git a/src/output.c b/src/output.c index ca6f53850582..cfd9a9c0bd6f 100644 --- a/src/output.c +++ b/src/output.c @@ -76,6 +76,7 @@ #include "output-json-sip.h" #include "output-json-rfb.h" #include "output-json-mqtt.h" +#include "output-json-pgsql.h" #include "output-json-template.h" #include "output-json-template-rust.h" #include "output-json-rdp.h" @@ -1110,6 +1111,8 @@ void OutputRegisterLoggers(void) JsonRFBLogRegister(); /* MQTT JSON logger. */ JsonMQTTLogRegister(); + /* PGSQL JSON logger. */ + JsonPGSQLLogRegister(); /* Template JSON logger. */ JsonTemplateLogRegister(); /* Template Rust JSON logger. */ diff --git a/src/suricata-common.h b/src/suricata-common.h index 160cde8173a3..096315657bfb 100644 --- a/src/suricata-common.h +++ b/src/suricata-common.h @@ -462,6 +462,7 @@ typedef enum { LOGGER_JSON_TEMPLATE_RUST, LOGGER_JSON_RFB, LOGGER_JSON_MQTT, + LOGGER_JSON_POSTGRESQL, LOGGER_JSON_TEMPLATE, LOGGER_JSON_RDP, LOGGER_JSON_DCERPC, diff --git a/src/util-profiling.c b/src/util-profiling.c index cc6d5c0856e8..956cb287d3ef 100644 --- a/src/util-profiling.c +++ b/src/util-profiling.c @@ -1319,6 +1319,7 @@ const char * PacketProfileLoggertIdToString(LoggerId id) CASE_CODE (LOGGER_JSON_TEMPLATE_RUST); CASE_CODE (LOGGER_JSON_RFB); CASE_CODE (LOGGER_JSON_MQTT); + CASE_CODE(LOGGER_JSON_POSTGRESQL); CASE_CODE (LOGGER_JSON_TEMPLATE); CASE_CODE (LOGGER_JSON_RDP); CASE_CODE (LOGGER_JSON_DCERPC); diff --git a/suricata.yaml.in b/suricata.yaml.in index 3f1bae8f11fd..bb7457ea71a6 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -149,6 +149,7 @@ outputs: header: X-Forwarded-For types: + - postgresql - alert: # payload: yes # enable dumping payload in Base64 # payload-buffer-size: 4kb # max size of payload buffer to output in eve-log @@ -714,6 +715,8 @@ pcap-file: # "detection-only" enables protocol detection only (parser disabled). app-layer: protocols: + pgsql: + enabled: yes rfb: enabled: yes detection-ports: