From ea503dd7adf16a9160a484a46e8aad6c47b58902 Mon Sep 17 00:00:00 2001 From: lqk Date: Thu, 19 Sep 2024 16:04:41 +0800 Subject: [PATCH] rust/mysql: implement logger --- rust/src/mysql/logger.rs | 117 ++++++++++++++++++++++++++ rust/src/mysql/mod.rs | 1 + src/Makefile.am | 2 + src/output-json-mysql.c | 175 +++++++++++++++++++++++++++++++++++++++ src/output-json-mysql.h | 29 +++++++ src/output.c | 3 + suricata.yaml.in | 1 + 7 files changed, 328 insertions(+) create mode 100644 rust/src/mysql/logger.rs create mode 100644 src/output-json-mysql.c create mode 100644 src/output-json-mysql.h diff --git a/rust/src/mysql/logger.rs b/rust/src/mysql/logger.rs new file mode 100644 index 000000000000..f378c88e33d4 --- /dev/null +++ b/rust/src/mysql/logger.rs @@ -0,0 +1,117 @@ +/* Copyright (C) 2022 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// written by linqiankai +// +use crate::jsonbuilder::{JsonBuilder, JsonError}; +use crate::mysql::mysql::*; + +fn log_mysql(tx: &MysqlTransaction, _flags: u32, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("mysql")?; + js.set_uint("tx_id", tx.tx_id)?; + if let Some(version) = &tx.version { + js.set_string("version", version)?; + } + if let Some(tls) = &tx.tls { + js.set_bool("tls", *tls)?; + } else { + js.set_bool("tls", false)?; + } + + if tx.command.is_some() { + let command = tx.command.clone().unwrap(); + js.set_string("command", &command)?; + } + if tx.affected_rows.is_some() { + let affected_rows = tx.affected_rows.unwrap(); + js.set_uint("affected_rows", affected_rows)?; + } + + js.close()?; + + Ok(()) +} + +fn log_mysql_alert( + tx: &MysqlTransaction, _flags: u32, js: &mut JsonBuilder, +) -> Result<(), JsonError> { + js.open_object("mysql")?; + js.set_uint("tx_id", tx.tx_id)?; + if let Some(version) = &tx.version { + js.set_string("version", version)?; + } + if let Some(tls) = &tx.tls { + js.set_bool("tls", *tls)?; + } else { + js.set_bool("tls", false)?; + } + + if tx.command.is_some() { + let command = tx.command.clone().unwrap(); + js.set_string("command", &command)?; + } + if tx.affected_rows.is_some() { + let affected_rows = tx.affected_rows.unwrap(); + js.set_uint("affected_rows", affected_rows)?; + } + if let Some(rows) = &tx.rows { + js.open_array("rows")?; + for row in rows { + js.append_string(row)?; + } + js.close()?; + } + + js.close()?; + + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mysql_logger( + tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx_mysql = cast_pointer!(tx, MysqlTransaction); + SCLogDebug!( + "----------- MySQL rs_mysql_logger call. Tx is {:?}", + tx_mysql + ); + let result = log_mysql(tx_mysql, flags, js); + if let Err(ref err) = result { + SCLogError!("----------- MySQL rs_mysql_logger failed. err is {:?}", err); + } + return result.is_ok(); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_mysql_logger_alert( + tx: *mut std::os::raw::c_void, flags: u32, js: &mut JsonBuilder, +) -> bool { + let tx_mysql = cast_pointer!(tx, MysqlTransaction); + SCLogDebug!( + "----------- MySQL rs_mysql_logger_alert call. Tx is {:?}", + tx_mysql + ); + let result = log_mysql_alert(tx_mysql, flags, js); + if let Err(ref err) = result { + SCLogError!( + "----------- MySQL rs_mysql_logger_alert failed. err is {:?}", + err + ); + } + return result.is_ok(); +} diff --git a/rust/src/mysql/mod.rs b/rust/src/mysql/mod.rs index 702e20db9111..96be7aa8c41c 100644 --- a/rust/src/mysql/mod.rs +++ b/rust/src/mysql/mod.rs @@ -18,5 +18,6 @@ //! MySQL parser, logger and application layer module. //! //! written by Kotodian +pub mod logger; pub mod mysql; pub mod parser; diff --git a/src/Makefile.am b/src/Makefile.am index 148821ea2b69..3347138f788a 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -396,6 +396,7 @@ noinst_HEADERS = \ output-json-smtp.h \ output-json-stats.h \ output-json-tls.h \ + output-json-mysql.h \ output-eve-syslog.h \ output-lua.h \ output-packet.h \ @@ -973,6 +974,7 @@ libsuricata_c_a_SOURCES = \ output-json-smtp.c \ output-json-stats.c \ output-json-tls.c \ + output-json-mysql.c \ output-eve.c \ output-eve-syslog.c \ output-eve-null.c \ diff --git a/src/output-json-mysql.c b/src/output-json-mysql.c new file mode 100644 index 000000000000..e5234b5c313a --- /dev/null +++ b/src/output-json-mysql.c @@ -0,0 +1,175 @@ +/* Copyright (C) 2022-2024 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 linqiankai + * + * Implement JSON/eve logging for app-layer MySQL. + */ + +#include "suricata-common.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 "output-json-mysql.h" +#include "rust.h" + +#define MYSQL_LOG_NULL BIT_U32(0) +#define MYSQL_DEFAULTS (MYSQL_LOG_NULL) + +typedef struct OutputMysqlCtx_ { + uint32_t flags; + OutputJsonCtx *eve_ctx; +} OutputMysqlCtx; + +typedef struct LogMysqlLogThread_ { + OutputMysqlCtx *mysqllog_ctx; + OutputJsonThreadCtx *ctx; +} LogMysqlLogThread; + +static int JsonMysqlLogger(ThreadVars *tv, void *thread_data, const Packet *p, Flow *f, void *state, + void *txptr, uint64_t tx_id) +{ + LogMysqlLogThread *thread = thread_data; + SCLogDebug("Logging mysql transaction %" PRIu64 ".", tx_id); + + JsonBuilder *jb = + CreateEveHeader(p, LOG_DIR_FLOW, "mysql", NULL, thread->mysqllog_ctx->eve_ctx); + if (unlikely(jb == NULL)) { + return TM_ECODE_FAILED; + } + + if (!rs_mysql_logger(txptr, thread->mysqllog_ctx->flags, jb)) { + goto error; + } + + OutputJsonBuilderBuffer(jb, thread->ctx); + jb_free(jb); + + return TM_ECODE_OK; + +error: + jb_free(jb); + return TM_ECODE_FAILED; +} + +static void OutputMysqlLogDeInitCtxSub(OutputCtx *output_ctx) +{ + OutputMysqlCtx *mysqllog_ctx = (OutputMysqlCtx *)output_ctx->data; + SCFree(mysqllog_ctx); + SCFree(output_ctx); +} + +static OutputInitResult OutputMysqlLogInitSub(ConfNode *conf, OutputCtx *parent_ctx) +{ + OutputInitResult result = { NULL, false }; + OutputJsonCtx *ojc = parent_ctx->data; + + OutputMysqlCtx *mysql_ctx = SCCalloc(1, sizeof(OutputMysqlCtx)); + if (unlikely(mysql_ctx == NULL)) + return result; + + OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); + if (unlikely(output_ctx == NULL)) { + SCFree(mysql_ctx); + return result; + } + + mysql_ctx->eve_ctx = ojc; + + output_ctx->data = mysql_ctx; + output_ctx->DeInit = OutputMysqlLogDeInitCtxSub; + + AppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_MYSQL); + + SCLogDebug("MySQL log sub-module initialized."); + + result.ctx = output_ctx; + result.ok = true; + return result; +} + +static TmEcode JsonMysqlLogThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + LogMysqlLogThread *thread = SCCalloc(1, sizeof(LogMysqlLogThread)); + if (unlikely(thread == NULL)) { + return TM_ECODE_FAILED; + } + + if (initdata == NULL) { + SCLogDebug("Error getting context for EveLogMysql. \"initdata\" is NULL."); + goto error_exit; + } + + thread->mysqllog_ctx = ((OutputCtx *)initdata)->data; + thread->ctx = CreateEveThreadCtx(t, thread->mysqllog_ctx->eve_ctx); + if (!thread->ctx) { + goto error_exit; + } + *data = (void *)thread; + + return TM_ECODE_OK; + +error_exit: + SCFree(thread); + return TM_ECODE_FAILED; +} + +static TmEcode JsonMysqlLogThreadDeinit(ThreadVars *t, void *data) +{ + LogMysqlLogThread *thread = (LogMysqlLogThread *)data; + if (thread == NULL) { + return TM_ECODE_OK; + } + FreeEveThreadCtx(thread->ctx); + SCFree(thread); + return TM_ECODE_OK; +} + +void JsonMysqlLogRegister(void) +{ + /* MYSQL_START_REMOVE */ + if (ConfGetNode("app-layer.protocols.mysql") == NULL) { + SCLogDebug("Disabling Mysql eve-logger"); + return; + } + /* MYSQL_END_REMOVE */ + /* Register as an eve sub-module. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonMysqlLog", "eve-log.mysql", + OutputMysqlLogInitSub, ALPROTO_MYSQL, JsonMysqlLogger, JsonMysqlLogThreadInit, + JsonMysqlLogThreadDeinit); + + SCLogDebug("MySQL JSON logger registered."); +} diff --git a/src/output-json-mysql.h b/src/output-json-mysql.h new file mode 100644 index 000000000000..91d7bec36338 --- /dev/null +++ b/src/output-json-mysql.h @@ -0,0 +1,29 @@ +/* Copyright (C) 2022-2024 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 linqiankai + */ + +#ifndef SURICATA_OUTPUT_JSON_MYSQL_H +#define SURICATA_OUTPUT_JSON_MYSQL_H + +void JsonMysqlLogRegister(void); + +#endif // !SURICATA_OUTPUT_JSON_MYSQL_H diff --git a/src/output.c b/src/output.c index 002f33b5abc6..f37a88ce8227 100644 --- a/src/output.c +++ b/src/output.c @@ -82,6 +82,7 @@ #include "app-layer-parser.h" #include "output-filestore.h" #include "output-json-arp.h" +#include "output-json-mysql.h" typedef struct RootLogger_ { OutputLogFunc LogFunc; @@ -1105,4 +1106,6 @@ void OutputRegisterLoggers(void) } /* ARP JSON logger */ JsonArpLogRegister(); + /* MYSQL JSON logger */ + JsonMysqlLogRegister(); } diff --git a/suricata.yaml.in b/suricata.yaml.in index 78f0fc4bbb84..6b846a7dcfc0 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -314,6 +314,7 @@ outputs: - sip - quic - ldap + - mysql - arp: enabled: no # Many events can be logged. Disabled by default - dhcp: