diff --git a/doc/userguide/upgrade.rst b/doc/userguide/upgrade.rst index 282c5a0cf1de..e8a3410c04b4 100644 --- a/doc/userguide/upgrade.rst +++ b/doc/userguide/upgrade.rst @@ -95,6 +95,9 @@ Major changes will need to be set before external modules can be loaded. See the new default configuration file or :ref:`lua-output-yaml` for more details. +- If the configuration value ``ftp.memcap`` is invalid, Suricata will set it to ``0`` which means + no limit will be placed. In previous Suricata releases, Suricata would terminate execution. A + warning message will be displayed `Invalid value for ftp.memcap` when this occurs. Removals ~~~~~~~~ diff --git a/rust/cbindgen.toml b/rust/cbindgen.toml index e2730789c808..7e20a8411a40 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 000000000000..2b3e8dd0887e --- /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 000000000000..3963a2adfa77 --- /dev/null +++ b/rust/src/ftp/ftp.rs @@ -0,0 +1,245 @@ +/* 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::conf::{conf_get, get_memval}; +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() + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn SCFTPGetConfigValues( + memcap: *mut u64, max_tx: *mut u32, max_line_len: *mut u32, +) { + if let Some(val) = conf_get("app-layer.protocols.ftp.memcap") { + if let Ok(v) = get_memval(val) { + *memcap = v; + SCLogConfig!("FTP memcap: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.memcap; defaulting to {}", + val, + *memcap + ); + } + } + if let Some(val) = conf_get("app-layer.protocols.ftp.max-tx") { + if let Ok(v) = val.parse::() { + *max_tx = v; + SCLogConfig!("FTP max tx: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.max-tx; defaulting to {}", + val, + *max_tx + ); + } + } + // This value is often expressed with a unit suffix, e.g., 5kb, hence get_memval + if let Some(val) = conf_get("app-layer.protocols.ftp.max-line-length") { + if let Ok(v) = get_memval(val) { + *max_line_len = v as u32; + SCLogConfig!("FTP max line length: {}", v); + } else { + SCLogWarning!( + "Invalid value {} for ftp.max-line-length; defaulting to {}", + val, + *max_line_len + ); + } + } +} + +/// 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 a206aa2f350c..b7b74732c9e7 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 a6a1f632fcb4..29df14e53094 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; @@ -115,41 +58,10 @@ static FTPTransaction *FTPGetOldestTx(const FtpState *, FTPTransaction *); static void FTPParseMemcap(void) { - const char *conf_val; - - /** set config values for memcap, prealloc and hash_size */ - if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1) - { - if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) { - SCLogError("Error parsing ftp.memcap " - "from conf file - %s. Killing engine", - conf_val); - exit(EXIT_FAILURE); - } - SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap); - } else { - /* default to unlimited */ - ftp_config_memcap = 0; - } + SCFTPGetConfigValues(&ftp_config_memcap, &ftp_config_maxtx, &ftp_max_line_len); SC_ATOMIC_INIT(ftp_memuse); SC_ATOMIC_INIT(ftp_memcap); - - if ((ConfGet("app-layer.protocols.ftp.max-tx", &conf_val)) == 1) { - if (ParseSizeStringU32(conf_val, &ftp_config_maxtx) < 0) { - SCLogError("Error parsing ftp.max-tx " - "from conf file - %s.", - conf_val); - } - SCLogInfo("FTP max tx: %" PRIu32, ftp_config_maxtx); - } - - if ((ConfGet("app-layer.protocols.ftp.max-line-length", &conf_val)) == 1) { - if (ParseSizeStringU32(conf_val, &ftp_max_line_len) < 0) { - SCLogError("Error parsing ftp.max-line-length from conf file - %s.", conf_val); - } - SCLogConfig("FTP max line length: %" PRIu32, ftp_max_line_len); - } } static void FTPIncrMemuse(uint64_t size) @@ -421,7 +333,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 +343,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 +441,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 +506,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 +645,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 +764,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 +885,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 +1000,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 +1212,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 e69415d8cf13..99d42b8b97ef 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 b106ed4ed847..f06e46cccb77 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 14232bdfe393..f7fae9983b76 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 {