From f0410c93d05b13e1da3cada8d72e44511d58c18c Mon Sep 17 00:00:00 2001 From: Jeff Lucovsky Date: Sun, 2 Feb 2025 09:12:59 -0500 Subject: [PATCH] ftp: convert enumerations to Rust As part of the effort to convert the FTP/FTPDATA parser to rust, move the enums from C to rust. Issue: 4082 --- rust/cbindgen.toml | 3 + rust/src/ftp/constant.rs | 89 +++++++++++++++++ rust/src/ftp/ftp.rs | 201 +++++++++++++++++++++++++++++++++++++++ rust/src/ftp/mod.rs | 4 +- src/app-layer-ftp.c | 150 ++++++++++------------------- src/app-layer-ftp.h | 89 +++-------------- src/detect-ftp-command.c | 13 ++- src/output-json-ftp.c | 36 ++++--- 8 files changed, 390 insertions(+), 195 deletions(-) create mode 100644 rust/src/ftp/constant.rs create mode 100644 rust/src/ftp/ftp.rs diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index e2730789c8..7e20a8411a 100644 --- a/rust/cbindgen.toml +++ b/rust/cbindgen.toml @@ -82,6 +82,9 @@ include = [ "QuicState", "QuicTransaction", "FtpEvent", + "FtpRequestCommand", + "FtpStateValues", + "FtpDataStateValues", "SCSigTableElmt", "SCTransformTableElmt", "DataRepType", diff --git a/rust/src/ftp/constant.rs b/rust/src/ftp/constant.rs new file mode 100644 index 0000000000..2b3e8dd088 --- /dev/null +++ b/rust/src/ftp/constant.rs @@ -0,0 +1,89 @@ +/* Copyright (C) 2025 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. + */ + +// FTP state progress values +#[repr(u8)] +#[allow(non_camel_case_types)] +pub enum FtpStateValues { + FTP_STATE_NONE, + FTP_STATE_IN_PROGRESS, + FTP_STATE_PORT_DONE, + FTP_STATE_FINISHED, +} +// FTP Data progress values +#[repr(u8)] +#[allow(non_camel_case_types)] +pub enum FtpDataStateValues { + FTPDATA_STATE_IN_PROGRESS = 1, + FTPDATA_STATE_FINISHED = 2, +} + +// FTP request command values +#[repr(u8)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy)] +pub enum FtpRequestCommand { + FTP_COMMAND_UNKNOWN, + FTP_COMMAND_ABOR, + FTP_COMMAND_ACCT, + FTP_COMMAND_ALLO, + FTP_COMMAND_APPE, + FTP_COMMAND_AUTH_TLS, + FTP_COMMAND_CDUP, + FTP_COMMAND_CHMOD, + FTP_COMMAND_CWD, + FTP_COMMAND_DELE, + FTP_COMMAND_EPSV, + FTP_COMMAND_HELP, + FTP_COMMAND_IDLE, + FTP_COMMAND_LIST, + FTP_COMMAND_MAIL, + FTP_COMMAND_MDTM, + FTP_COMMAND_MKD, + FTP_COMMAND_MLFL, + FTP_COMMAND_MODE, + FTP_COMMAND_MRCP, + FTP_COMMAND_MRSQ, + FTP_COMMAND_MSAM, + FTP_COMMAND_MSND, + FTP_COMMAND_MSOM, + FTP_COMMAND_NLST, + FTP_COMMAND_NOOP, + FTP_COMMAND_PASS, + FTP_COMMAND_PASV, + FTP_COMMAND_PORT, + FTP_COMMAND_PWD, + FTP_COMMAND_QUIT, + FTP_COMMAND_REIN, + FTP_COMMAND_REST, + FTP_COMMAND_RETR, + FTP_COMMAND_RMD, + FTP_COMMAND_RNFR, + FTP_COMMAND_RNTO, + FTP_COMMAND_SITE, + FTP_COMMAND_SIZE, + FTP_COMMAND_SMNT, + FTP_COMMAND_STAT, + FTP_COMMAND_STOR, + FTP_COMMAND_STOU, + FTP_COMMAND_STRU, + FTP_COMMAND_SYST, + FTP_COMMAND_TYPE, + FTP_COMMAND_UMASK, + FTP_COMMAND_USER, + FTP_COMMAND_EPRT, +} diff --git a/rust/src/ftp/ftp.rs b/rust/src/ftp/ftp.rs new file mode 100644 index 0000000000..2cc4566077 --- /dev/null +++ b/rust/src/ftp/ftp.rs @@ -0,0 +1,201 @@ +/* Copyright (C) 2025 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. + */ + +use std; +use std::ffi::CString; +use std::os::raw::{c_char, c_int, c_void}; + +use crate::ftp::constant::*; +use lazy_static::lazy_static; + +/// cbindgen:ignore +#[repr(C)] +pub struct FtpCommand { + command_name: CString, + command: FtpRequestCommand, + command_length: u8, +} + +impl FtpCommand { + fn new(command_name: &str, command: FtpRequestCommand) -> FtpCommand { + let cstring = CString::new(command_name).unwrap(); + let length = cstring.as_bytes().len(); + FtpCommand { + command_name: cstring, + command, + command_length: length as u8, + } + } +} + +lazy_static! { + static ref FTP_COMMANDS: Vec = vec![ + FtpCommand::new("PORT", FtpRequestCommand::FTP_COMMAND_PORT), + FtpCommand::new("EPRT", FtpRequestCommand::FTP_COMMAND_EPRT), + FtpCommand::new("AUTH_TLS", FtpRequestCommand::FTP_COMMAND_AUTH_TLS), + FtpCommand::new("PASV", FtpRequestCommand::FTP_COMMAND_PASV), + FtpCommand::new("EPSV", FtpRequestCommand::FTP_COMMAND_EPSV), + FtpCommand::new("RETR", FtpRequestCommand::FTP_COMMAND_RETR), + FtpCommand::new("STOR", FtpRequestCommand::FTP_COMMAND_STOR), + FtpCommand::new("ABOR", FtpRequestCommand::FTP_COMMAND_ABOR), + FtpCommand::new("ACCT", FtpRequestCommand::FTP_COMMAND_ACCT), + FtpCommand::new("ALLO", FtpRequestCommand::FTP_COMMAND_ALLO), + FtpCommand::new("APPE", FtpRequestCommand::FTP_COMMAND_APPE), + FtpCommand::new("CDUP", FtpRequestCommand::FTP_COMMAND_CDUP), + FtpCommand::new("CHMOD", FtpRequestCommand::FTP_COMMAND_CHMOD), + FtpCommand::new("CWD", FtpRequestCommand::FTP_COMMAND_CWD), + FtpCommand::new("DELE", FtpRequestCommand::FTP_COMMAND_DELE), + FtpCommand::new("HELP", FtpRequestCommand::FTP_COMMAND_HELP), + FtpCommand::new("IDLE", FtpRequestCommand::FTP_COMMAND_IDLE), + FtpCommand::new("LIST", FtpRequestCommand::FTP_COMMAND_LIST), + FtpCommand::new("MAIL", FtpRequestCommand::FTP_COMMAND_MAIL), + FtpCommand::new("MDTM", FtpRequestCommand::FTP_COMMAND_MDTM), + FtpCommand::new("MKD", FtpRequestCommand::FTP_COMMAND_MKD), + FtpCommand::new("MLFL", FtpRequestCommand::FTP_COMMAND_MLFL), + FtpCommand::new("MODE", FtpRequestCommand::FTP_COMMAND_MODE), + FtpCommand::new("MRCP", FtpRequestCommand::FTP_COMMAND_MRCP), + FtpCommand::new("MRSQ", FtpRequestCommand::FTP_COMMAND_MRSQ), + FtpCommand::new("MSAM", FtpRequestCommand::FTP_COMMAND_MSAM), + FtpCommand::new("MSND", FtpRequestCommand::FTP_COMMAND_MSND), + FtpCommand::new("MSOM", FtpRequestCommand::FTP_COMMAND_MSOM), + FtpCommand::new("NLST", FtpRequestCommand::FTP_COMMAND_NLST), + FtpCommand::new("NOOP", FtpRequestCommand::FTP_COMMAND_NOOP), + FtpCommand::new("PASS", FtpRequestCommand::FTP_COMMAND_PASS), + FtpCommand::new("PWD", FtpRequestCommand::FTP_COMMAND_PWD), + FtpCommand::new("QUIT", FtpRequestCommand::FTP_COMMAND_QUIT), + FtpCommand::new("REIN", FtpRequestCommand::FTP_COMMAND_REIN), + FtpCommand::new("REST", FtpRequestCommand::FTP_COMMAND_REST), + FtpCommand::new("RMD", FtpRequestCommand::FTP_COMMAND_RMD), + FtpCommand::new("RNFR", FtpRequestCommand::FTP_COMMAND_RNFR), + FtpCommand::new("RNTO", FtpRequestCommand::FTP_COMMAND_RNTO), + FtpCommand::new("SITE", FtpRequestCommand::FTP_COMMAND_SITE), + FtpCommand::new("SIZE", FtpRequestCommand::FTP_COMMAND_SIZE), + FtpCommand::new("SMNT", FtpRequestCommand::FTP_COMMAND_SMNT), + FtpCommand::new("STAT", FtpRequestCommand::FTP_COMMAND_STAT), + FtpCommand::new("STOU", FtpRequestCommand::FTP_COMMAND_STOU), + FtpCommand::new("STRU", FtpRequestCommand::FTP_COMMAND_STRU), + FtpCommand::new("SYST", FtpRequestCommand::FTP_COMMAND_SYST), + FtpCommand::new("TYPE", FtpRequestCommand::FTP_COMMAND_TYPE), + FtpCommand::new("UMASK", FtpRequestCommand::FTP_COMMAND_UMASK), + FtpCommand::new("USER", FtpRequestCommand::FTP_COMMAND_USER), + FtpCommand::new("UNKNOWN", FtpRequestCommand::FTP_COMMAND_UNKNOWN), + ]; +} + +/// cbindgen:ignore +extern "C" { + pub fn MpmAddPatternCI( + ctx: *const c_void, pat: *const libc::c_char, pat_len: c_int, _offset: c_int, + _depth: c_int, id: c_int, rule_id: c_int, _flags: c_int, + ) -> c_void; +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn SCGetFtpCommandInfo( + index: usize, name_ptr: *mut *const c_char, code_ptr: *mut u8, len_ptr: *mut u8, +) -> bool { + if index <= FTP_COMMANDS.len() { + unsafe { + if !name_ptr.is_null() { + *name_ptr = FTP_COMMANDS[index].command_name.as_ptr(); + } + if !code_ptr.is_null() { + *code_ptr = FTP_COMMANDS[index].command as u8; + } + if !len_ptr.is_null() { + *len_ptr = FTP_COMMANDS[index].command_length; + } + } + true + } else { + false + } +} + +#[allow(non_snake_case)] +#[no_mangle] +pub unsafe extern "C" fn SCFTPSetMpmState(ctx: *const c_void) { + for index in 0..FTP_COMMANDS.len() { + let name_ptr = FTP_COMMANDS[index].command_name.as_ptr(); + let len = FTP_COMMANDS[index].command_length; + if len > 0 { + MpmAddPatternCI( + ctx, + name_ptr, + len as c_int, + 0, + 0, + index as c_int, + index as c_int, + 0, + ); + } + } +} + +#[repr(C)] +#[allow(dead_code)] +pub struct FtpTransferCmd { + // Must be first -- required by app-layer expectation logic + data_free: unsafe extern "C" fn(*mut c_void), + pub flow_id: u64, + pub file_name: *mut u8, + pub file_len: u16, + pub direction: u8, + pub cmd: u8, +} + +impl Default for FtpTransferCmd { + fn default() -> Self { + FtpTransferCmd { + flow_id: 0, + file_name: std::ptr::null_mut(), + file_len: 0, + direction: 0, + cmd: FtpStateValues::FTP_STATE_NONE as u8, + data_free: default_free_fn, + } + } +} + +unsafe extern "C" fn default_free_fn(_ptr: *mut c_void) {} +impl FtpTransferCmd { + pub fn new() -> Self { + FtpTransferCmd { + ..Default::default() + } + } +} + +/// Returns *mut FtpTransferCmd +#[no_mangle] +pub unsafe extern "C" fn SCFTPTransferCmdNew() -> *mut FtpTransferCmd { + SCLogDebug!("allocating ftp transfer cmd"); + let cmd = FtpTransferCmd::new(); + Box::into_raw(Box::new(cmd)) +} + +/// Params: +/// - transfer command: *mut FTPTransferCmd as void pointer +#[no_mangle] +pub unsafe extern "C" fn SCFTPTransferCmdFree(cmd: *mut FtpTransferCmd) { + SCLogDebug!("freeing ftp transfer cmd"); + if !cmd.is_null() { + let _transfer_cmd = Box::from_raw(cmd); + } +} diff --git a/rust/src/ftp/mod.rs b/rust/src/ftp/mod.rs index a206aa2f35..b7b74732c9 100644 --- a/rust/src/ftp/mod.rs +++ b/rust/src/ftp/mod.rs @@ -26,7 +26,9 @@ use std; use std::str; use std::str::FromStr; +pub mod constant; pub mod event; +pub mod ftp; // We transform an integer string into a i64, ignoring surrounding whitespaces // We look for a digit suite, and try to convert it. @@ -111,7 +113,7 @@ pub unsafe extern "C" fn rs_ftp_pasv_response(input: *const u8, len: u32) -> u16 if input.is_null() { return 0; } - let buf = build_slice!(input, len as usize); + let buf = build_slice!(input, len as usize); match ftp_pasv_response(buf) { Ok((_, dport)) => { return dport; diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index a6a1f632fc..682ee2b42a 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2024 Open Information Security Foundation +/* Copyright (C) 2007-2025 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 @@ -47,63 +47,6 @@ typedef struct FTPThreadCtx_ { static MpmCtx *ftp_mpm_ctx = NULL; -// clang-format off -const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = { - /* Parsed and handled */ - { "PORT", FTP_COMMAND_PORT, 4 }, - { "EPRT", FTP_COMMAND_EPRT, 4 }, - { "AUTH TLS", FTP_COMMAND_AUTH_TLS, 8 }, - { "PASV", FTP_COMMAND_PASV, 4 }, - { "RETR", FTP_COMMAND_RETR, 4 }, - { "EPSV", FTP_COMMAND_EPSV, 4 }, - { "STOR", FTP_COMMAND_STOR, 4 }, - - /* Parsed, but not handled */ - { "ABOR", FTP_COMMAND_ABOR, 4 }, - { "ACCT", FTP_COMMAND_ACCT, 4 }, - { "ALLO", FTP_COMMAND_ALLO, 4 }, - { "APPE", FTP_COMMAND_APPE, 4 }, - { "CDUP", FTP_COMMAND_CDUP, 4 }, - { "CHMOD", FTP_COMMAND_CHMOD, 5 }, - { "CWD", FTP_COMMAND_CWD, 3 }, - { "DELE", FTP_COMMAND_DELE, 4 }, - { "HELP", FTP_COMMAND_HELP, 4 }, - { "IDLE", FTP_COMMAND_IDLE, 4 }, - { "LIST", FTP_COMMAND_LIST, 4 }, - { "MAIL", FTP_COMMAND_MAIL, 4 }, - { "MDTM", FTP_COMMAND_MDTM, 4 }, - { "MKD", FTP_COMMAND_MKD, 3 }, - { "MLFL", FTP_COMMAND_MLFL, 4 }, - { "MODE", FTP_COMMAND_MODE, 4 }, - { "MRCP", FTP_COMMAND_MRCP, 4 }, - { "MRSQ", FTP_COMMAND_MRSQ, 4 }, - { "MSAM", FTP_COMMAND_MSAM, 4 }, - { "MSND", FTP_COMMAND_MSND, 4 }, - { "MSOM", FTP_COMMAND_MSOM, 4 }, - { "NLST", FTP_COMMAND_NLST, 4 }, - { "NOOP", FTP_COMMAND_NOOP, 4 }, - { "PASS", FTP_COMMAND_PASS, 4 }, - { "PWD", FTP_COMMAND_PWD, 3 }, - { "QUIT", FTP_COMMAND_QUIT, 4 }, - { "REIN", FTP_COMMAND_REIN, 4 }, - { "REST", FTP_COMMAND_REST, 4 }, - { "RMD", FTP_COMMAND_RMD, 3 }, - { "RNFR", FTP_COMMAND_RNFR, 4 }, - { "RNTO", FTP_COMMAND_RNTO, 4 }, - { "SITE", FTP_COMMAND_SITE, 4 }, - { "SIZE", FTP_COMMAND_SIZE, 4 }, - { "SMNT", FTP_COMMAND_SMNT, 4 }, - { "STAT", FTP_COMMAND_STAT, 4 }, - { "STOU", FTP_COMMAND_STOU, 4 }, - { "STRU", FTP_COMMAND_STRU, 4 }, - { "SYST", FTP_COMMAND_SYST, 4 }, - { "TYPE", FTP_COMMAND_TYPE, 4 }, - { "UMASK", FTP_COMMAND_UMASK, 5 }, - { "USER", FTP_COMMAND_USER, 4 }, - { NULL, FTP_COMMAND_UNKNOWN, 0 } -}; -// clang-format on - uint64_t ftp_config_memcap = 0; uint32_t ftp_config_maxtx = 1024; uint32_t ftp_max_line_len = 4096; @@ -421,7 +364,7 @@ static AppLayerResult FTPGetLineForDirection( * \retval 1 when the command is parsed, 0 otherwise */ static int FTPParseRequestCommand( - FTPThreadCtx *td, FtpLineState *line, const FtpCommand **cmd_descriptor) + FTPThreadCtx *td, FtpLineState *line, FtpCommandInfo *cmd_descriptor) { SCEnter(); @@ -431,34 +374,40 @@ static int FTPParseRequestCommand( int mpm_cnt = mpm_table[FTP_MPM].Search( ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, line->buf, line->len); if (mpm_cnt) { - *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; - SCReturnInt(1); + uint8_t command_code; + if (SCGetFtpCommandInfo(td->pmq->rule_id_array[0], NULL, &command_code, NULL)) { + cmd_descriptor->command_code = command_code; + /* FTP command indices are expressed in Rust as a u8 */ + cmd_descriptor->command_index = (uint8_t)td->pmq->rule_id_array[0]; + SCReturnInt(1); + } else { + /* Where is out command? */ + DEBUG_VALIDATE_BUG_ON(1); + } +#ifdef DEBUG + if (SCLogDebugEnabled()) { + const char *command_name = NULL; + (void)SCGetFtpCommandInfo(td->pmq->rule_id_array[0], &command_name, NULL, NULL); + SCLogDebug("matching FTP command is %s [code: %d, index %d]", command_name, + command_code, td->pmq->rule_id_array[0]); + } +#endif } - *cmd_descriptor = NULL; + cmd_descriptor->command_code = FTP_COMMAND_UNKNOWN; SCReturnInt(0); } -struct FtpTransferCmd { - /** Need to look like a ExpectationData so DFree must - * be first field . */ - void (*DFree)(void *); - uint64_t flow_id; - uint8_t *file_name; - uint16_t file_len; - uint8_t direction; /**< direction in which the data will flow */ - FtpRequestCommand cmd; -}; - static void FtpTransferCmdFree(void *data) { - struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; + FtpTransferCmd *cmd = (FtpTransferCmd *)data; if (cmd == NULL) return; if (cmd->file_name) { - FTPFree(cmd->file_name, cmd->file_len + 1); + FTPFree((void *)cmd->file_name, cmd->file_len + 1); } - FTPFree(cmd, sizeof(struct FtpTransferCmd)); + SCFTPTransferCmdFree(cmd); + FTPDecrMemuse((uint64_t)sizeof(FtpTransferCmd)); } static uint32_t CopyCommandLine(uint8_t **dest, FtpLineState *line) @@ -523,14 +472,14 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt } else if (res.status == -1) { break; } - const FtpCommand *cmd_descriptor; + FtpCommandInfo cmd_descriptor; if (!FTPParseRequestCommand(thread_data, &line, &cmd_descriptor)) { state->command = FTP_COMMAND_UNKNOWN; continue; } - state->command = cmd_descriptor->command; + state->command = cmd_descriptor.command_code; FTPTransaction *tx = FTPTransactionCreate(state); if (unlikely(tx == NULL)) SCReturnStruct(APP_LAYER_ERROR); @@ -588,10 +537,12 @@ static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserSt if (state->dyn_port == 0 || line.len < 6) { SCReturnStruct(APP_LAYER_ERROR); } - struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); + FtpTransferCmd *data = SCFTPTransferCmdNew(); if (data == NULL) SCReturnStruct(APP_LAYER_ERROR); - data->DFree = FtpTransferCmdFree; + FTPIncrMemuse((uint64_t)(sizeof *data)); + data->data_free = FtpTransferCmdFree; + /* * Min size has been checked in FTPParseRequestCommand * SC_FILENAME_MAX includes the null @@ -725,9 +676,9 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS } lasttx = tx; tx->tx_data.updated_tc = true; - if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { + if (state->command == FTP_COMMAND_UNKNOWN) { /* unknown */ - tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX - 1]; + tx->command_descriptor.command_code = FTP_COMMAND_UNKNOWN; } state->curr_tx = tx; @@ -844,9 +795,17 @@ static void FTPStateFree(void *s) FTPTransaction *tx = NULL; while ((tx = TAILQ_FIRST(&fstate->tx_list))) { TAILQ_REMOVE(&fstate->tx_list, tx, next); - SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p", - tx->command_descriptor->command_name, s, tx->tx_id, tx->request_length, - tx->request); +#ifdef DEBUG + if (SCLogDebugEnabled()) { + const char *command_name = NULL; + (void)SCGetFtpCommandInfo( + tx->command_descriptor.command_index, &command_name, NULL, NULL); + SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p", + command_name != NULL ? command_name : "n/a", s, tx->tx_id, tx->request_length, + tx->request); + } +#endif + FTPTransactionFree(tx); } @@ -957,7 +916,8 @@ static int FTPGetAlstateProgress(void *vtx, uint8_t direction) FTPTransaction *tx = vtx; if (!tx->done) { - if (direction == STREAM_TOSERVER && tx->command_descriptor->command == FTP_COMMAND_PORT) { + if (direction == STREAM_TOSERVER && + tx->command_descriptor.command_code == FTP_COMMAND_PORT) { return FTP_STATE_PORT_DONE; } return FTP_STATE_IN_PROGRESS; @@ -1071,8 +1031,8 @@ static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, SCLogDebug("FTP-DATA flags %04x dir %d", flags, direction); if (input_len && ftpdata_state->files == NULL) { - struct FtpTransferCmd *data = - (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); + FtpTransferCmd *data = + (FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); if (data == NULL) { SCReturnStruct(APP_LAYER_ERROR); } @@ -1283,19 +1243,7 @@ static void FTPSetMpmState(void) } MpmInitCtx(ftp_mpm_ctx, FTP_MPM); - uint32_t i = 0; - for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { - const FtpCommand *cmd = &FtpCommands[i]; - if (cmd->command_length == 0) - continue; - - MpmAddPatternCI(ftp_mpm_ctx, - (uint8_t *)cmd->command_name, - cmd->command_length, - 0 /* defunct */, 0 /* defunct */, - i /* id */, i /* rule id */ , 0 /* no flags */); - } - + SCFTPSetMpmState(ftp_mpm_ctx); mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); } diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index e69415d8cf..99d42b8b97 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2021 Open Information Security Foundation +/* Copyright (C) 2007-2025 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 @@ -27,74 +27,7 @@ #include "rust.h" -enum { - FTP_STATE_IN_PROGRESS, - FTP_STATE_PORT_DONE, - FTP_STATE_FINISHED, -}; - -typedef enum { - FTP_COMMAND_UNKNOWN = 0, - FTP_COMMAND_ABOR, - FTP_COMMAND_ACCT, - FTP_COMMAND_ALLO, - FTP_COMMAND_APPE, - FTP_COMMAND_AUTH_TLS, - FTP_COMMAND_CDUP, - FTP_COMMAND_CHMOD, - FTP_COMMAND_CWD, - FTP_COMMAND_DELE, - FTP_COMMAND_EPSV, - FTP_COMMAND_HELP, - FTP_COMMAND_IDLE, - FTP_COMMAND_LIST, - FTP_COMMAND_MAIL, - FTP_COMMAND_MDTM, - FTP_COMMAND_MKD, - FTP_COMMAND_MLFL, - FTP_COMMAND_MODE, - FTP_COMMAND_MRCP, - FTP_COMMAND_MRSQ, - FTP_COMMAND_MSAM, - FTP_COMMAND_MSND, - FTP_COMMAND_MSOM, - FTP_COMMAND_NLST, - FTP_COMMAND_NOOP, - FTP_COMMAND_PASS, - FTP_COMMAND_PASV, - FTP_COMMAND_PORT, - FTP_COMMAND_PWD, - FTP_COMMAND_QUIT, - FTP_COMMAND_REIN, - FTP_COMMAND_REST, - FTP_COMMAND_RETR, - FTP_COMMAND_RMD, - FTP_COMMAND_RNFR, - FTP_COMMAND_RNTO, - FTP_COMMAND_SITE, - FTP_COMMAND_SIZE, - FTP_COMMAND_SMNT, - FTP_COMMAND_STAT, - FTP_COMMAND_STOR, - FTP_COMMAND_STOU, - FTP_COMMAND_STRU, - FTP_COMMAND_SYST, - FTP_COMMAND_TYPE, - FTP_COMMAND_UMASK, - FTP_COMMAND_USER, - FTP_COMMAND_EPRT, - - /* must be last */ - FTP_COMMAND_MAX - /** \todo more if missing.. */ -} FtpRequestCommand; - -typedef struct FtpCommand_ { - const char *command_name; - FtpRequestCommand command; - const uint8_t command_length; -} FtpCommand; -extern const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1]; +struct FtpCommand; typedef uint32_t FtpRequestCommandArgOfs; @@ -115,6 +48,17 @@ typedef struct FTPString_ { TAILQ_ENTRY(FTPString_) next; } FTPString; +/* + * These are the values for the table index value and the FTP command + * enum value. These *should* be the same if the enum and command insertion + * order remain the same. However, we store each value to protect against + * drift between enum and insertion order. + */ +typedef struct FtpCommandInfo_ { + uint8_t command_index; + FtpRequestCommand command_code; +} FtpCommandInfo; + typedef struct FTPTransaction_ { /** id of this tx, starting at 0 */ uint64_t tx_id; @@ -127,7 +71,7 @@ typedef struct FTPTransaction_ { bool request_truncated; /* for the command description */ - const FtpCommand *command_descriptor; + FtpCommandInfo command_descriptor; uint16_t dyn_port; /* dynamic port, if applicable */ bool done; /* transaction complete? */ @@ -163,11 +107,6 @@ typedef struct FtpState_ { AppLayerStateData state_data; } FtpState; -enum { - FTPDATA_STATE_IN_PROGRESS, - FTPDATA_STATE_FINISHED, -}; - /** FTP Data State for app layer parser */ typedef struct FtpDataState_ { uint8_t *input; diff --git a/src/detect-ftp-command.c b/src/detect-ftp-command.c index b106ed4ed8..f06e46cccb 100644 --- a/src/detect-ftp-command.c +++ b/src/detect-ftp-command.c @@ -67,13 +67,16 @@ static InspectionBuffer *GetData(DetectEngineThreadCtx *det_ctx, if (buffer->inspect == NULL) { FTPTransaction *tx = (FTPTransaction *)txv; - if (tx->command_descriptor->command_name == NULL || - tx->command_descriptor->command_length == 0) + if (tx->command_descriptor.command_code == FTP_COMMAND_UNKNOWN) return NULL; - InspectionBufferSetupAndApplyTransforms(det_ctx, list_id, buffer, - (const uint8_t *)tx->command_descriptor->command_name, - tx->command_descriptor->command_length, transforms); + const char *b = NULL; + uint8_t b_len = 0; + + if (SCGetFtpCommandInfo(tx->command_descriptor.command_index, &b, NULL, &b_len)) { + InspectionBufferSetupAndApplyTransforms( + det_ctx, list_id, buffer, (const uint8_t *)b, b_len, transforms); + } } return buffer; diff --git a/src/output-json-ftp.c b/src/output-json-ftp.c index 14232bdfe3..f7fae9983b 100644 --- a/src/output-json-ftp.c +++ b/src/output-json-ftp.c @@ -58,18 +58,28 @@ bool EveFTPLogCommand(void *vtx, JsonBuilder *jb) return false; } } + const char *command_name = NULL; + uint8_t command_name_length; + if (tx->command_descriptor.command_code != FTP_COMMAND_UNKNOWN) { + if (!SCGetFtpCommandInfo(tx->command_descriptor.command_index, &command_name, NULL, + &command_name_length)) { + SCLogDebug("Unable to fetch info for FTP command code %d [index %d]", + tx->command_descriptor.command_code, tx->command_descriptor.command_index); + return false; + } + } jb_open_object(jb, "ftp"); - jb_set_string(jb, "command", tx->command_descriptor->command_name); - uint32_t min_length = tx->command_descriptor->command_length + 1; /* command + space */ - if (tx->request_length > min_length) { - jb_set_string_from_bytes(jb, - "command_data", - (const uint8_t *)tx->request + min_length, - tx->request_length - min_length - 1); - if (tx->request_truncated) { - JB_SET_TRUE(jb, "command_truncated"); - } else { - JB_SET_FALSE(jb, "command_truncated"); + if (command_name) { + jb_set_string(jb, "command", command_name); + uint32_t min_length = command_name_length + 1; /* command + space */ + if (tx->request_length > min_length) { + jb_set_string_from_bytes(jb, "command_data", (const uint8_t *)tx->request + min_length, + tx->request_length - min_length - 1); + if (tx->request_truncated) { + JB_SET_TRUE(jb, "command_truncated"); + } else { + JB_SET_FALSE(jb, "command_truncated"); + } } } @@ -131,8 +141,8 @@ bool EveFTPLogCommand(void *vtx, JsonBuilder *jb) jb_set_uint(jb, "dynamic_port", tx->dyn_port); } - if (tx->command_descriptor->command == FTP_COMMAND_PORT || - tx->command_descriptor->command == FTP_COMMAND_EPRT) { + if (tx->command_descriptor.command_code == FTP_COMMAND_PORT || + tx->command_descriptor.command_code == FTP_COMMAND_EPRT) { if (tx->active) { JB_SET_STRING(jb, "mode", "active"); } else {