Skip to content

Commit e1d4685

Browse files
committed
smtp: start a new transaction on subsequent helo
The SMTP RFC states that a mid-session HELO is to be treated like a RSET. So on a subsequent HELO/EHLO, start a new transaction. We had to move the ownership of the HELO buffer from state to the transaction as on a subsequent HELO we have record the new HELO value, as well as store the old one until the old transaction is fully evaluated. Ticket: OISF#8715
1 parent 97cc4fd commit e1d4685

4 files changed

Lines changed: 81 additions & 23 deletions

File tree

src/app-layer-smtp.c

Lines changed: 62 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,20 @@ static void SMTPSetEvent(SMTPState *s, uint8_t e)
526526
SCLogDebug("couldn't set event %u", e);
527527
}
528528

529-
static SMTPTransaction *SMTPTransactionCreate(SMTPState *state)
529+
static uint8_t *SMTPCopyParam(const uint8_t *src, uint16_t len)
530+
{
531+
uint8_t *copy = SCMalloc(len + 1);
532+
if (copy == NULL) {
533+
return NULL;
534+
}
535+
if (len > 0) {
536+
memcpy(copy, src, len);
537+
}
538+
copy[len] = '\0';
539+
return copy;
540+
}
541+
542+
static SMTPTransaction *SMTPTransactionCreate(SMTPState *state, bool inherit_helo)
530543
{
531544
if (state->tx_cnt > smtp_config.max_tx) {
532545
return NULL;
@@ -538,6 +551,14 @@ static SMTPTransaction *SMTPTransactionCreate(SMTPState *state)
538551

539552
TAILQ_INIT(&tx->rcpt_to_list);
540553
tx->tx_data.file_tx = STREAM_TOSERVER; // can xfer files
554+
if (inherit_helo && state->helo != NULL) {
555+
tx->helo = SMTPCopyParam(state->helo, state->helo_len);
556+
if (tx->helo == NULL) {
557+
SCFree(tx);
558+
return NULL;
559+
}
560+
tx->helo_len = state->helo_len;
561+
}
541562
return tx;
542563
}
543564

@@ -1140,11 +1161,24 @@ static int SMTPParseCommandWithParam(SMTPState *state, const SMTPLine *line, uin
11401161

11411162
static int SMTPParseCommandHELO(SMTPState *state, const SMTPLine *line)
11421163
{
1143-
if (state->helo) {
1164+
if (state->curr_tx->helo) {
11441165
SMTPSetEvent(state, SMTP_DECODER_EVENT_DUPLICATE_FIELDS);
11451166
return 0;
11461167
}
1147-
return SMTPParseCommandWithParam(state, line, 4, &state->helo, &state->helo_len);
1168+
int r = SMTPParseCommandWithParam(
1169+
state, line, 4, &state->curr_tx->helo, &state->curr_tx->helo_len);
1170+
if (r == 0) {
1171+
uint8_t *helo = SMTPCopyParam(state->curr_tx->helo, state->curr_tx->helo_len);
1172+
if (helo == NULL) {
1173+
return -1;
1174+
}
1175+
if (state->helo) {
1176+
SCFree(state->helo);
1177+
}
1178+
state->helo = helo;
1179+
state->helo_len = state->curr_tx->helo_len;
1180+
}
1181+
return r;
11481182
}
11491183

11501184
static int SMTPParseCommandMAILFROM(SMTPState *state, const SMTPLine *line)
@@ -1178,6 +1212,12 @@ static int SMTPParseCommandRCPTTO(SMTPState *state, const SMTPLine *line)
11781212
return 0;
11791213
}
11801214

1215+
static bool SMTPLineIsHelo(const SMTPLine *line)
1216+
{
1217+
return line->len >= 4 && (SCMemcmpLowercase("helo", line->buf, 4) == 0 ||
1218+
SCMemcmpLowercase("ehlo", line->buf, 4) == 0);
1219+
}
1220+
11811221
/* consider 'rset' and 'quit' to be part of the existing state */
11821222
static int NoNewTx(SMTPState *state, const SMTPLine *line)
11831223
{
@@ -1235,9 +1275,15 @@ static int SMTPProcessRequest(
12351275
if (line->len == 0 && line->delim_len == 0) {
12361276
return 0;
12371277
}
1238-
if (state->curr_tx == NULL ||
1278+
const bool command_mode = !(state->parser_state & SMTP_PARSER_STATE_COMMAND_DATA_MODE);
1279+
const bool helo_cmd = command_mode && SMTPLineIsHelo(line);
1280+
const bool subsequent_helo = helo_cmd && state->seen_helo;
1281+
if (subsequent_helo && state->curr_tx != NULL && !SMTPTransactionIsComplete(state->curr_tx)) {
1282+
SMTPTransactionComplete(state);
1283+
}
1284+
if (state->curr_tx == NULL || subsequent_helo ||
12391285
(SMTPTransactionIsComplete(state->curr_tx) && !NoNewTx(state, line))) {
1240-
tx = SMTPTransactionCreate(state);
1286+
tx = SMTPTransactionCreate(state, !subsequent_helo);
12411287
if (tx == NULL)
12421288
return -1;
12431289
state->curr_tx = tx;
@@ -1313,12 +1359,12 @@ static int SMTPProcessRequest(
13131359
state->current_command = SMTP_COMMAND_BDAT;
13141360
SMTPSetProgressTS(tx, SMTP_REQUEST_DATA);
13151361
state->parser_state |= SMTP_PARSER_STATE_COMMAND_DATA_MODE;
1316-
} else if (line->len >= 4 && ((SCMemcmpLowercase("helo", line->buf, 4) == 0) ||
1317-
SCMemcmpLowercase("ehlo", line->buf, 4) == 0)) {
1362+
} else if (helo_cmd) {
13181363
r = SMTPParseCommandHELO(state, line);
13191364
if (r == -1) {
13201365
SCReturnInt(-1);
13211366
}
1367+
state->seen_helo = true;
13221368
state->current_command = SMTP_COMMAND_OTHER_CMD;
13231369
} else if (line->len >= 9 && SCMemcmpLowercase("mail from", line->buf, 9) == 0) {
13241370
r = SMTPParseCommandMAILFROM(state, line);
@@ -1665,6 +1711,10 @@ static void SMTPTransactionFree(SMTPTransaction *tx, SMTPState *state)
16651711

16661712
SCAppLayerTxDataCleanup(&tx->tx_data);
16671713

1714+
if (tx->helo) {
1715+
SCFree(tx->helo);
1716+
}
1717+
16681718
if (tx->mail_from)
16691719
SCFree(tx->mail_from);
16701720

@@ -4121,8 +4171,10 @@ static int SMTPParserTest14(void)
41214171
goto end;
41224172
}
41234173

4124-
if ((smtp_state->helo_len != 7) || strncmp("boo.com", (char *)smtp_state->helo, 7)) {
4125-
printf("incorrect parsing of HELO field '%s' (%d)\n", smtp_state->helo, smtp_state->helo_len);
4174+
if ((smtp_state->curr_tx->helo_len != 7) ||
4175+
strncmp("boo.com", (char *)smtp_state->curr_tx->helo, 7)) {
4176+
printf("incorrect parsing of HELO field '%s' (%d)\n", smtp_state->curr_tx->helo,
4177+
smtp_state->curr_tx->helo_len);
41264178
goto end;
41274179
}
41284180

@@ -4352,6 +4404,7 @@ static int SMTPParserTest14(void)
43524404
StreamTcpFreeConfig(true);
43534405
return result;
43544406
}
4407+
43554408
#endif /* UNITTESTS */
43564409

43574410
void SMTPParserRegisterTests(void)

src/app-layer-smtp.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ typedef struct SMTPTransaction_ {
9999
/** the mime decoding parser state */
100100
MimeStateSMTP *mime_state;
101101

102+
/* HELO/EHLO parameter */
103+
uint8_t *helo;
104+
uint16_t helo_len;
105+
102106
/* MAIL FROM parameters */
103107
uint8_t *mail_from;
104108
uint16_t mail_from_len;
@@ -161,9 +165,11 @@ typedef struct SMTPState_ {
161165
* handler */
162166
uint16_t cmds_idx;
163167

164-
/* HELO of HELO message content */
165-
uint16_t helo_len;
168+
/* Last HELO/EHLO parameter seen on the flow */
166169
uint8_t *helo;
170+
uint16_t helo_len;
171+
/* flow has seen at least one HELO/EHLO command */
172+
bool seen_helo;
167173

168174
/* SMTP Mime decoding and file extraction */
169175
/** the list of files sent to the server */

src/detect-smtp.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,12 @@ static InspectionBuffer *GetSmtpHeloData(DetectEngineThreadCtx *det_ctx,
5555
{
5656
InspectionBuffer *buffer = SCInspectionBufferGet(det_ctx, list_id);
5757
if (buffer->inspect == NULL) {
58-
SMTPState *smtp_state = (SMTPState *)FlowGetAppState(f);
59-
if (smtp_state) {
60-
if (smtp_state->helo == NULL || smtp_state->helo_len == 0)
61-
return NULL;
62-
InspectionBufferSetup(det_ctx, list_id, buffer, smtp_state->helo, smtp_state->helo_len);
63-
InspectionBufferApplyTransforms(det_ctx, buffer, transforms);
58+
SMTPTransaction *tx = txv;
59+
if (tx == NULL || tx->helo == NULL || tx->helo_len == 0) {
60+
return NULL;
6461
}
62+
InspectionBufferSetup(det_ctx, list_id, buffer, tx->helo, tx->helo_len);
63+
InspectionBufferApplyTransforms(det_ctx, buffer, transforms);
6564
}
6665
return buffer;
6766
}

src/output-json-smtp.c

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,16 @@
5151
#include "output-json-smtp.h"
5252
#include "output-json-email-common.h"
5353

54-
static void EveSmtpDataLogger(void *state, void *vtx, SCJsonBuilder *js)
54+
static void EveSmtpDataLogger(void *vtx, SCJsonBuilder *js)
5555
{
56-
if (state == NULL || vtx == NULL) {
56+
if (vtx == NULL) {
5757
return;
5858
}
5959

6060
SMTPTransaction *tx = vtx;
6161
SMTPString *rcptto_str;
62-
if (((SMTPState *)state)->helo) {
63-
SCJbSetString(js, "helo", (const char *)((SMTPState *)state)->helo);
62+
if (tx->helo) {
63+
SCJbSetString(js, "helo", (const char *)tx->helo);
6464
}
6565
if (tx->mail_from) {
6666
SCJbSetString(js, "mail_from", (const char *)tx->mail_from);
@@ -85,7 +85,7 @@ static int JsonSmtpLogger(ThreadVars *tv, void *thread_data, const Packet *p, Fl
8585
return TM_ECODE_OK;
8686

8787
SCJbOpenObject(jb, "smtp");
88-
EveSmtpDataLogger(state, tx, jb);
88+
EveSmtpDataLogger(tx, jb);
8989
SCJbClose(jb);
9090

9191
EveEmailLogJson(jhl, jb, p, f, state, tx, tx_id);
@@ -103,7 +103,7 @@ bool EveSMTPAddMetadata(const Flow *f, uint64_t tx_id, SCJsonBuilder *js)
103103
if (smtp_state) {
104104
SMTPTransaction *tx = AppLayerParserGetTx(IPPROTO_TCP, ALPROTO_SMTP, smtp_state, tx_id);
105105
if (tx) {
106-
EveSmtpDataLogger(smtp_state, tx, js);
106+
EveSmtpDataLogger(tx, js);
107107
return true;
108108
}
109109
}

0 commit comments

Comments
 (0)