imap: implement parser

This introduces a parser for IMAP protocol, replacing the
detection-only implementation.

An IMAP transaction has two states driven by the 'complete' field:
  - Open (complete = false): waiting for a matching tagged response.
  - Complete (complete = true): tagged response received, or special
    conditions met (BYE, server greeting).

  Completion logic (is_complete):
  - If a tagged request exists: requires a matching tagged response.
  - No tagged request + BYE: complete (server closing).
  - No tagged request + any response: complete (server greeting).

  Transactions are created in three places:
  1. Request parser: every parsed command creates a new tx.
  2. Response parser (tagged, no matching request): midstream/async;
     orphan tagged response gets its own tx.
  3. Response parser (untagged, no incomplete tx): server greeting or
     unsolicited response.

  Once open, messages accumulate:
  - Request side: continuation and literal data attach to the most
    recent incomplete tx.
  - Response side: untagged responses attach to the most recent
    incomplete tx; tagged responses attach via find_request(tag).

  Literals, sent by the client (e.g. APPEND) or by the server (e.g.
  FETCH), are consumed incrementally and their content is not retained.

  Hard limits prevent unbounded growth:
  - IMAP_MAX_TX (256, configurable): total transactions. Exceeded:
    all incomplete txs force-completed with TooManyTransactions event.
  - IMAP_MAX_MSGS_PER_TX (512): requests per tx. Exceeded: request
    dropped, DataLimitReached event.
  - IMAP_MAX_LINES (512): response lines per tx. Exceeded: line
    dropped, DataLimitReached event.
  - IMAP_MAX_LINE_BYTES_PER_STATE (100MB): retained request and
    response data per flow. Exceeded: data dropped, DataLimitReached
    event.
  - IMAP_MAX_LINE_SIZE (8KB): request/response line max length.
    Exceeded: LineTooLong event.
  - Literal size: bounded by declared size in {N} specifier.

  IMAP_MAX_TX only is configurable limit via app-layer.protocols.imap.max-tx.

Ticket #8276
pull/16515/head
Giuseppe Longo 9 months ago
parent 67f2f243f7
commit 1d7da06d9f

@ -12,6 +12,7 @@ files.rules \
ftp-events.rules \
http-events.rules \
http2-events.rules \
imap-events.rules \
ipsec-events.rules \
kerberos-events.rules \
ldap-events.rules \

@ -39,6 +39,7 @@ signature IDs.
| PGSQL | 2241000 | 2241999 |
| mDNS | 2242000 | 2242999 |
| Bittorent| 2243000 | 2243999 |
| IMAP | 2244000 | 2244999 |
| MODBUS | 2250000 | 2250999 |
| DNP3 | 2270000 | 2270999 |
| SIP | 2280000 | 2280999 |

@ -0,0 +1,8 @@
# IMAP app layer event rules
#
# SID's fall in the 2244000+ range. See rules/README.md
alert imap any any -> any any (msg:"SURICATA IMAP too many transactions"; app-layer-event:imap.too_many_transactions; classtype:protocol-command-decode; sid:2244000; rev:1;)
alert imap any any -> any any (msg:"SURICATA IMAP invalid data"; app-layer-event:imap.invalid_data; classtype:protocol-command-decode; sid:2244001; rev:1;)
alert imap any any -> any any (msg:"SURICATA IMAP line too long"; app-layer-event:imap.line_too_long; classtype:protocol-command-decode; sid:2244002; rev:1;)
alert imap any any -> any any (msg:"SURICATA IMAP data limit reached"; app-layer-event:imap.data_limit_reached; classtype:protocol-command-decode; sid:2244003; rev:1;)

File diff suppressed because it is too large Load Diff

@ -1,4 +1,4 @@
/* Copyright (C) 2024 Open Information Security Foundation
/* 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
@ -15,14 +15,7 @@
* 02110-1301, USA.
*/
/**
* \file
*
* \author Mahmoud Maatuq <mahmoudmatook.mm@gmail.com>
*
*/
// Author: Giuseppe Longo <glongo@oisf.net>
#ifndef SURICATA_APP_LAYER_IMAP_H
#define SURICATA_APP_LAYER_IMAP_H
void RegisterIMAPParsers(void);
#endif
pub mod imap;
pub mod parser;

File diff suppressed because it is too large Load Diff

@ -140,6 +140,7 @@ pub mod util;
pub mod websocket;
pub mod x509;
pub use suricata_ffi::direction;
pub mod imap;
pub mod llmnr;
#[allow(unused_imports)]

@ -37,7 +37,6 @@ noinst_HEADERS = \
app-layer-htp.h \
app-layer-http2.h \
app-layer-ike.h \
app-layer-imap.h \
app-layer-modbus.h \
app-layer-nfs-tcp.h \
app-layer-nfs-udp.h \
@ -615,7 +614,6 @@ libsuricata_c_a_SOURCES = \
app-layer-htp.c \
app-layer-http2.c \
app-layer-ike.c \
app-layer-imap.c \
app-layer-modbus.c \
app-layer-nfs-tcp.c \
app-layer-nfs-udp.c \

@ -1,133 +0,0 @@
/* Copyright (C) 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 Mahmoud Maatuq <mahmoudmatook.mm@gmail.com>
*
*/
#include "suricata-common.h"
#include "app-layer-detect-proto.h"
#include "app-layer-imap.h"
#include "app-layer-protos.h"
#include "rust.h"
#include "stream-tcp-private.h"
#include "stream-tcp-reassemble.h"
#include "util-debug.h"
static AppProto ImapClientProbingParser(
const Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
{
// This is called after " CAPABILITY" pattern has been found
if (f->alproto_tc == ALPROTO_IMAP) {
// server was already recognised as IMAP, so ok
return ALPROTO_IMAP;
}
for (size_t i = 0; i < len; i++) {
if (input[i] >= '0' && input[i] <= '9') {
// We check if the tag at the beginning of the input contains a number
return ALPROTO_IMAP;
}
if (input[i] == ' ') {
break;
}
}
TcpSession *ssn = (TcpSession *)f->protoctx;
if (ssn == NULL) {
return ALPROTO_FAILED;
}
const uint32_t size_tc = StreamDataAvailableForProtoDetect(&ssn->server);
if (size_tc < 8 && f->alproto_tc == ALPROTO_UNKNOWN) {
// wait for another packet from server
return ALPROTO_UNKNOWN;
}
return ALPROTO_FAILED;
}
static int IMAPRegisterPatternsForProtocolDetection(void)
{
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* OK ", 5, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* NO ", 5, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* BAD ", 6, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* LIST ", 7, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* ESEARCH ", 10, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* STATUS ", 9, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
if (SCAppLayerProtoDetectPMRegisterPatternCI(
IPPROTO_TCP, ALPROTO_IMAP, "* FLAGS ", 8, 0, STREAM_TOCLIENT) < 0) {
return -1;
}
/**
* there is no official document that limits the length of the tag
* some practical implementations limit it to 20 characters
* but keeping depth equal to 31 fails unit tests such AppLayerTest10
* so keeping depth 17 for now to pass unit tests, that might miss some detections
* until we find a better solution for the unit tests.
*
* AppLayerTest10 fails because it expects protocol detection to be completed with only 17 bytes
* as input, and with this new pattern, we would need more bytes to finish protocol detection.
*/
if (SCAppLayerProtoDetectPMRegisterPatternCIwPP(IPPROTO_TCP, ALPROTO_IMAP, " CAPABILITY",
17 /*6 for max tag len + space + len(CAPABILITY)*/, 0, STREAM_TOSERVER,
ImapClientProbingParser, 12, 17) < 0) {
return -1;
}
return 0;
}
void RegisterIMAPParsers(void)
{
const char *proto_name = "imap";
if (SCAppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
SCLogDebug("IMAP protocol detection is enabled.");
AppLayerProtoDetectRegisterProtocol(ALPROTO_IMAP, proto_name);
if (IMAPRegisterPatternsForProtocolDetection() < 0)
SCLogError("Failed to register IMAP protocol detection patterns.");
} else {
SCLogDebug("Protocol detector and parser disabled for IMAP.");
}
}

@ -55,7 +55,6 @@
#include "app-layer-tftp.h"
#include "app-layer-ike.h"
#include "app-layer-http2.h"
#include "app-layer-imap.h"
#include "conf.h"
#include "decode.h"
#include "detect.h"
@ -2056,9 +2055,9 @@ void AppLayerParserRegisterProtocolParsers(void)
SCRegisterRdpParser();
RegisterHTTP2Parsers();
SCRegisterTelnetParser();
RegisterIMAPParsers();
SCRegisterLLMNRUdpParser();
SCRegisterLLMNRTcpParser();
SCRegisterImapParser();
for (size_t i = 0; i < preregistered_callbacks_nb; i++) {
PreRegisteredCallbacks[i]();

@ -1082,7 +1082,8 @@ app-layer:
content-inspect-min-size: 32768
content-inspect-window: 4096
imap:
enabled: detection-only
enabled: yes
# max-tx: 256
pop3:
enabled: yes
detection-ports:

Loading…
Cancel
Save