From d674ce25103ef9cee3240b3fc1bf9d90a0a8473a Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Fri, 21 Mar 2025 10:01:27 -0400 Subject: [PATCH] app/ftp: Use Rust FTP response line handling Use the Rust logic to parse FTP response lines with the goal to support multi-buffer matches better. A side effect is that the completion codes are no longer strings; the schema update reflects this. Issue: 4082 --- src/app-layer-ftp.c | 59 +++++++++++++++++++++++++------------------ src/app-layer-ftp.h | 12 ++++----- src/output-json-ftp.c | 48 +++++++++++++---------------------- 3 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index 707a8d5b01..7541214335 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -20,7 +20,7 @@ * * \author Pablo Rincon Crespo * \author Eric Leblond - * \author Jeff Lucovsky + * \author Jeff Lucovsky * * App Layer Parser for FTP */ @@ -130,14 +130,13 @@ static void *FTPCalloc(size_t n, size_t size) static void *FTPRealloc(void *ptr, size_t orig_size, size_t size) { - void *rptr = NULL; - + DEBUG_VALIDATE_BUG_ON(size == 0); if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) { sc_errno = SC_ELIMIT; return NULL; } - rptr = SCRealloc(ptr, size); + void *rptr = SCRealloc(ptr, size); if (rptr == NULL) { sc_errno = SC_ENOMEM; return NULL; @@ -159,18 +158,24 @@ static void FTPFree(void *ptr, size_t size) FTPDecrMemuse((uint64_t)size); } -static FTPString *FTPStringAlloc(void) +static FTPResponseWrapper *FTPResponseWrapperAlloc(FTPResponseLine *response) { - return FTPCalloc(1, sizeof(FTPString)); + FTPResponseWrapper *wrapper = FTPCalloc(1, sizeof(FTPResponseWrapper)); + if (likely(wrapper)) { + FTPIncrMemuse(response->total_size); + wrapper->response = response; + } + return wrapper; } -static void FTPStringFree(FTPString *str) +static void FTPResponseWrapperFree(FTPResponseWrapper *wrapper) { - if (str->str) { - FTPFree(str->str, str->len); + if (wrapper->response) { + FTPDecrMemuse(wrapper->response->total_size); + SCFTPFreeResponseLine(wrapper->response); } - FTPFree(str, sizeof(FTPString)); + FTPFree(wrapper, sizeof(FTPResponseWrapper)); } static void *FTPLocalStorageAlloc(void) @@ -244,10 +249,10 @@ static void FTPTransactionFree(FTPTransaction *tx) FTPFree(tx->request, tx->request_length); } - FTPString *str = NULL; - while ((str = TAILQ_FIRST(&tx->response_list))) { - TAILQ_REMOVE(&tx->response_list, str, next); - FTPStringFree(str); + FTPResponseWrapper *wrapper; + while ((wrapper = TAILQ_FIRST(&tx->response_list))) { + TAILQ_REMOVE(&tx->response_list, wrapper, next); + FTPResponseWrapperFree(wrapper); } FTPFree(tx, sizeof(*tx)); @@ -693,18 +698,24 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS } if (likely(line.len)) { - FTPString *response = FTPStringAlloc(); + FTPResponseLine *response = SCFTPParseResponseLine((const char *)line.buf, line.len); if (likely(response)) { - response->len = CopyCommandLine(&response->str, &line); - response->truncated = state->current_line_truncated_tc; - if (response->truncated) { - AppLayerDecoderEventsSetEventRaw( - &tx->tx_data.events, FtpEventResponseCommandTooLong); - } - if (line.lf_found) { - state->current_line_truncated_tc = false; + FTPResponseWrapper *wrapper = FTPResponseWrapperAlloc(response); + if (likely(wrapper)) { + response->truncated = state->current_line_truncated_tc; + if (response->truncated) { + AppLayerDecoderEventsSetEventRaw( + &tx->tx_data.events, FtpEventResponseCommandTooLong); + } + if (line.lf_found) { + state->current_line_truncated_tc = false; + } + TAILQ_INSERT_TAIL(&tx->response_list, wrapper, next); + } else { + SCFTPFreeResponseLine(response); } - TAILQ_INSERT_TAIL(&tx->response_list, response, next); + } else { + SCLogDebug("unable to parse FTP response line \"%s\"", line.buf); } } diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index 7c704c1949..99252137bc 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -41,12 +41,10 @@ typedef struct FtpLineState_ { bool lf_found; } FtpLineState; -typedef struct FTPString_ { - uint8_t *str; - uint32_t len; - bool truncated; - TAILQ_ENTRY(FTPString_) next; -} FTPString; +typedef struct FTPResponseWrapper_ { + FTPResponseLine *response; + TAILQ_ENTRY(FTPResponseWrapper_) next; +} FTPResponseWrapper; /* * These are the values for the table index value and the FTP command @@ -80,7 +78,7 @@ typedef struct FTPTransaction_ { uint8_t direction; /* Handle multiple responses */ - TAILQ_HEAD(, FTPString_) response_list; + TAILQ_HEAD(, FTPResponseWrapper_) response_list; TAILQ_ENTRY(FTPTransaction_) next; } FTPTransaction; diff --git a/src/output-json-ftp.c b/src/output-json-ftp.c index c623b5e6c1..19b50ef947 100644 --- a/src/output-json-ftp.c +++ b/src/output-json-ftp.c @@ -87,44 +87,30 @@ bool EveFTPLogCommand(void *vtx, SCJsonBuilder *jb) if (!TAILQ_EMPTY(&tx->response_list)) { int resp_cnt = 0; - FTPString *response; + FTPResponseWrapper *wrapper; bool is_cc_array_open = false; - TAILQ_FOREACH(response, &tx->response_list, next) { + TAILQ_FOREACH (wrapper, &tx->response_list, next) { /* handle multiple lines within the response, \r\n delimited */ - uint8_t *where = response->str; - uint16_t length = 0; - uint16_t pos; - if (response->len > 0 && response->len <= UINT16_MAX) { - length = (uint16_t)response->len - 1; - } else if (response->len > UINT16_MAX) { - length = UINT16_MAX; + if (!wrapper->response) { + continue; } + FTPResponseLine *response = wrapper->response; + if (!reply_truncated && response->truncated) { reply_truncated = true; } - while ((pos = JsonGetNextLineFromBuffer((const char *)where, length)) != UINT16_MAX) { - uint16_t offset = 0; - /* Try to find a completion code for this line */ - if (pos >= 3) { - /* Gather the completion code if present */ - if (isdigit(where[0]) && isdigit(where[1]) && isdigit(where[2])) { - if (!is_cc_array_open) { - SCJbOpenArray(jb, "completion_code"); - is_cc_array_open = true; - } - SCJbAppendStringFromBytes(jb, (const uint8_t *)where, 3); - offset = 4; - } - } - /* move past 3 character completion code */ - if (pos >= offset) { - SCJbAppendStringFromBytes( - js_resplist, (const uint8_t *)where + offset, pos - offset); - resp_cnt++; + int code_len = strlen((const char *)response->code); + if (code_len > 0) { + if (!is_cc_array_open) { + SCJbOpenArray(jb, "completion_code"); + is_cc_array_open = true; } - - where += pos; - length -= pos; + SCJbAppendStringFromBytes(jb, (const uint8_t *)response->code, code_len); + } + if (response->length) { + SCJbAppendStringFromBytes( + js_resplist, (const uint8_t *)response->response, response->length); + resp_cnt++; } } -- 2.47.3