From c3d583349b234c8d73734d1cdc779bb3fb8384b4 Mon Sep 17 00:00:00 2001 From: Giuseppe Longo Date: Fri, 23 Jan 2026 10:36:44 +0100 Subject: [PATCH] imap: implement logger This introduces a logger for IMAP protocol. Ticket #8276 --- etc/schema.json | 21 +++++++++ rust/src/imap/logger.rs | 97 +++++++++++++++++++++++++++++++++++++++++ rust/src/imap/mod.rs | 1 + src/output.c | 5 +++ suricata.yaml.in | 1 + 5 files changed, 125 insertions(+) create mode 100644 rust/src/imap/logger.rs diff --git a/etc/schema.json b/etc/schema.json index 4cff77cefb..f355ecb03e 100644 --- a/etc/schema.json +++ b/etc/schema.json @@ -2832,6 +2832,27 @@ } } }, + "imap": { + "type": "object", + "description": "IMAP transaction data", + "additionalProperties": false, + "properties": { + "requests": { + "type": "array", + "description": "Array of IMAP request lines in this transaction", + "items": { + "type": "string" + } + }, + "responses": { + "type": "array", + "description": "Array of IMAP response lines in this transaction", + "items": { + "type": "string" + } + } + } + }, "in_iface": { "type": "string" }, diff --git a/rust/src/imap/logger.rs b/rust/src/imap/logger.rs new file mode 100644 index 0000000000..6ff4d1657c --- /dev/null +++ b/rust/src/imap/logger.rs @@ -0,0 +1,97 @@ +/* Copyright (C) 2026 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +// Author: Giuseppe Longo + +use std::borrow::Cow; + +use crate::imap::imap::ImapTransaction; +use crate::imap::parser::{ImapCommand, ImapMessage, ImapMessageType}; +use crate::jsonbuilder::{JsonBuilder, JsonError}; + +const REDACTED: &str = ""; + +fn log_request_line( + request: &ImapMessage, suppress_continuation_data: bool, +) -> Option> { + if request.raw_line.is_empty() { + return None; + } + + match &request.message { + ImapMessageType::ContinuationData if suppress_continuation_data => None, + ImapMessageType::Command { + command: ImapCommand::Login, + arguments, + .. + } if !arguments.is_empty() => { + let tag = request.tag_str(); + Some(Cow::Owned(format!("{tag} LOGIN {REDACTED}"))) + } + ImapMessageType::Command { + command: ImapCommand::Authenticate, + arguments, + .. + } if arguments.len() > 1 => { + let tag = request.tag_str(); + let mechanism = String::from_utf8_lossy(&arguments[0]); + Some(Cow::Owned(format!( + "{tag} AUTHENTICATE {mechanism} {REDACTED}" + ))) + } + _ => Some(String::from_utf8_lossy(&request.raw_line)), + } +} + +fn log_imap(tx: &ImapTransaction, js: &mut JsonBuilder) -> Result<(), JsonError> { + js.open_object("imap")?; + + if !tx.requests.is_empty() { + js.open_array("requests")?; + + let suppress_continuation_data = tx.command.eq_ignore_ascii_case(b"AUTHENTICATE") + || tx.command.eq_ignore_ascii_case(b"LOGIN"); + for req in &tx.requests { + if let Some(line) = log_request_line(req, suppress_continuation_data) { + js.append_string(&line)?; + } + } + + js.close()?; + } + + if !tx.response_lines.is_empty() { + js.open_array("responses")?; + + for line in &tx.response_lines { + js.append_string(&String::from_utf8_lossy(line))?; + } + js.close()?; + } + + js.close()?; + + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn SCImapLoggerLog( + tx: *mut std::os::raw::c_void, js: &mut JsonBuilder, +) -> bool { + let tx = cast_pointer!(tx, ImapTransaction); + log_imap(tx, js).is_ok() +} diff --git a/rust/src/imap/mod.rs b/rust/src/imap/mod.rs index 91f3c0d300..739931da27 100644 --- a/rust/src/imap/mod.rs +++ b/rust/src/imap/mod.rs @@ -18,4 +18,5 @@ // Author: Giuseppe Longo pub mod imap; +pub mod logger; pub mod parser; diff --git a/src/output.c b/src/output.c index 651a6eb644..ca9f87ea24 100644 --- a/src/output.c +++ b/src/output.c @@ -990,6 +990,7 @@ void OutputRegisterRootLoggers(void) // underscore instead of dash for bittorrent_dht RegisterSimpleJsonApplayerLogger(ALPROTO_BITTORRENT_DHT, (EveJsonSimpleTxLogFunc)SCBittorrentDhtLogger, "bittorrent_dht"); + RegisterSimpleJsonApplayerLogger(ALPROTO_IMAP, (EveJsonSimpleTxLogFunc)SCImapLoggerLog, NULL); OutputPacketLoggerRegister(); OutputFiledataLoggerRegister(); @@ -1236,6 +1237,10 @@ void OutputRegisterLoggers(void) JsonArpLogRegister(); /* LLMNR JSON logger */ JsonLLMNRLogRegister(); + /* IMAP JSON logger. */ + OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", "JsonImapLog", "eve-log.imap", + OutputJsonLogInitSub, ALPROTO_IMAP, JsonGenericDirFlowLogger, JsonLogThreadInit, + JsonLogThreadDeinit); for (size_t i = 0; i < preregistered_loggers_nb; i++) { OutputRegisterTxSubModule(LOGGER_JSON_TX, "eve-log", preregistered_loggers[i].logname, diff --git a/suricata.yaml.in b/suricata.yaml.in index ed76e86c7e..4719ad308b 100644 --- a/suricata.yaml.in +++ b/suricata.yaml.in @@ -347,6 +347,7 @@ outputs: - pop3 - llmnr #- modbus + - imap - arp: enabled: no # Many events can be logged. Disabled by default - dhcp: