diff --git a/rust/src/ftp/ftp.rs b/rust/src/ftp/ftp.rs index c4da2b7b5835..b2f763b66f6f 100644 --- a/rust/src/ftp/ftp.rs +++ b/rust/src/ftp/ftp.rs @@ -16,36 +16,117 @@ */ use std; +use std::ffi::CString; use std::os::raw::{c_char, c_void}; -use crate::ftp::constant::*; use crate::conf::{conf_get, get_memval}; +use crate::ftp::constant::*; +use lazy_static::lazy_static; #[repr(C)] -//#[derive(Debug, Copy, Clone)] -pub struct FtpCommand { - pub command_name: *const c_char, - pub command: u8, - pub command_length: u8, + +/// cbindgen:ignore +struct FtpCommand { + command_name: CString, + command: u8, + command_length: u8, } impl FtpCommand { - pub fn new() -> Self { + fn new(command_name: &str, command: u8, command_length: u8) -> FtpCommand { + let cstring = CString::new(command_name).unwrap(); FtpCommand { - ..Default::default() + command_name: cstring, + command, + command_length, } } } -impl Default for FtpCommand { - fn default() -> Self { - FtpCommand { - command_name: std::ptr::null_mut(), - command: 0, - command_length: 0, + +lazy_static! { + static ref FTP_COMMANDS: Vec = vec![ + FtpCommand::new("PORT", FTP_COMMAND_PORT, 4), + FtpCommand::new("EPRT", FTP_COMMAND_EPRT, 4), + FtpCommand::new("AUTH_TLS", FTP_COMMAND_AUTH_TLS, 8), + FtpCommand::new("PASV", FTP_COMMAND_PASV, 4), + FtpCommand::new("EPSV", FTP_COMMAND_EPSV, 4), + FtpCommand::new("RETR", FTP_COMMAND_RETR, 4), + FtpCommand::new("STOR", FTP_COMMAND_STOR, 4), + FtpCommand::new("ABOR", FTP_COMMAND_ABOR, 4), + FtpCommand::new("ACCT", FTP_COMMAND_ACCT, 4), + FtpCommand::new("ALLO", FTP_COMMAND_ALLO, 4), + FtpCommand::new("APPE", FTP_COMMAND_APPE, 4), + FtpCommand::new("CDUP", FTP_COMMAND_CDUP, 4), + FtpCommand::new("CHMOD", FTP_COMMAND_CHMOD, 5), + FtpCommand::new("CWD", FTP_COMMAND_CWD, 3), + FtpCommand::new("DELE", FTP_COMMAND_DELE, 4), + FtpCommand::new("HELP", FTP_COMMAND_HELP, 4), + FtpCommand::new("IDLE", FTP_COMMAND_IDLE, 4), + FtpCommand::new("LIST", FTP_COMMAND_LIST, 4), + FtpCommand::new("MAIL", FTP_COMMAND_MAIL, 4), + FtpCommand::new("MDTM", FTP_COMMAND_MDTM, 4), + FtpCommand::new("MKD", FTP_COMMAND_MKD, 3), + FtpCommand::new("MLFL", FTP_COMMAND_MLFL, 4), + FtpCommand::new("MODE", FTP_COMMAND_MODE, 4), + FtpCommand::new("MRCP", FTP_COMMAND_MRCP, 4), + FtpCommand::new("MRSQ", FTP_COMMAND_MRSQ, 4), + FtpCommand::new("MSAM", FTP_COMMAND_MSAM, 4), + FtpCommand::new("MSND", FTP_COMMAND_MSND, 4), + FtpCommand::new("MSOM", FTP_COMMAND_MSOM, 4), + FtpCommand::new("NLST", FTP_COMMAND_NLST, 4), + FtpCommand::new("NOOP", FTP_COMMAND_NOOP, 4), + FtpCommand::new("PASS", FTP_COMMAND_PASS, 4), + FtpCommand::new("PWD", FTP_COMMAND_PWD, 3), + FtpCommand::new("QUIT", FTP_COMMAND_QUIT, 4), + FtpCommand::new("REIN", FTP_COMMAND_REIN, 4), + FtpCommand::new("REST", FTP_COMMAND_REST, 4), + FtpCommand::new("RMD", FTP_COMMAND_RMD, 3), + FtpCommand::new("RNFR", FTP_COMMAND_RNFR, 4), + FtpCommand::new("RNTO", FTP_COMMAND_RNTO, 4), + FtpCommand::new("SITE", FTP_COMMAND_SITE, 4), + FtpCommand::new("SIZE", FTP_COMMAND_SIZE, 4), + FtpCommand::new("SMNT", FTP_COMMAND_SMNT, 4), + FtpCommand::new("STAT", FTP_COMMAND_STAT, 4), + FtpCommand::new("STOU", FTP_COMMAND_STOU, 4), + FtpCommand::new("STRU", FTP_COMMAND_STRU, 4), + FtpCommand::new("SYST", FTP_COMMAND_SYST, 4), + FtpCommand::new("TYPE", FTP_COMMAND_TYPE, 4), + FtpCommand::new("UMASK", FTP_COMMAND_UMASK, 5), + FtpCommand::new("USER", FTP_COMMAND_USER, 4), + FtpCommand::new("UNKNOWN", FTP_COMMAND_UNKNOWN, 7), + FtpCommand::new("MAX", FTP_COMMAND_MAX, 0), + ]; +} + +#[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; + } + if !len_ptr.is_null() { + *len_ptr = FTP_COMMANDS[index].command_length; + } } + true + } else { + false } } +#[allow(non_snake_case)] +#[no_mangle] +pub extern "C" fn SCGetFtpCommandTableSize() -> usize { + FTP_COMMANDS.len() +} + #[repr(C)] #[allow(dead_code)] pub struct FtpTransferCmd { @@ -81,7 +162,9 @@ impl FtpTransferCmd { } #[no_mangle] -pub unsafe extern "C" fn SCFTPGetConfigValues(memcap: *mut u64, max_tx: *mut u32, max_line_len: *mut u32) { +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; diff --git a/src/app-layer-ftp.c b/src/app-layer-ftp.c index a040e418281d..24e043affafa 100644 --- a/src/app-layer-ftp.c +++ b/src/app-layer-ftp.c @@ -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; @@ -380,7 +323,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(); @@ -390,11 +333,19 @@ 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; + const char *command_name; + if (SCGetFtpCommandInfo(td->pmq->rule_id_array[0], &command_name, &command_code, NULL)) { + SCLogDebug("matching FTP command is %s [code: %d, index %d]", command_name, + command_code, td->pmq->rule_id_array[0]); + cmd_descriptor->command_code = command_code; + /* FTP command indices are < FTP_COMMAND_MAX and will fit into a u8 */ + cmd_descriptor->command_index = (uint8_t) td->pmq->rule_id_array[0]; + SCReturnInt(1); + } } - *cmd_descriptor = NULL; + cmd_descriptor->command_code = FTP_COMMAND_UNKNOWN; SCReturnInt(0); } @@ -472,14 +423,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); @@ -675,9 +626,8 @@ static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserS SCReturnStruct(APP_LAYER_ERROR); } lasttx = tx; - if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { - /* unknown */ - tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX - 1]; + if (state->command == FTP_COMMAND_UNKNOWN) { + tx->command_descriptor.command_code = FTP_COMMAND_UNKNOWN; } state->curr_tx = tx; @@ -794,9 +744,15 @@ 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); + if (sc_log_global_log_level >= SC_LOG_DEBUG) { + 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); + } + FTPTransactionFree(tx); } @@ -907,7 +863,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; @@ -1230,16 +1187,23 @@ 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) + const size_t commands_count = SCGetFtpCommandTableSize(); + for (i = 0; i < commands_count; i++) { + const char *command_name; + uint8_t command_length; + + if (!SCGetFtpCommandInfo(i, &command_name, NULL, &command_length)) { + SCLogError("Failed to obtain info for FTP command index %d", i); 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 */); + SCLogDebug("Adding MPM state for FTP command %s [length %d, index %d]", command_name, + command_length, i); + + if (command_length) { + MpmAddPatternCI(ftp_mpm_ctx, (uint8_t *)command_name, command_length, 0 /* defunct */, + 0 /* defunct */, i /* id */, i /* rule id */, 0 /* no flags */); + } } mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); diff --git a/src/app-layer-ftp.h b/src/app-layer-ftp.h index 45e12fd626c8..2dc032bb7fd7 100644 --- a/src/app-layer-ftp.h +++ b/src/app-layer-ftp.h @@ -50,6 +50,11 @@ typedef struct FTPString_ { TAILQ_ENTRY(FTPString_) next; } FTPString; +typedef struct FtpCommandInfo_ { + uint8_t command_code; + uint8_t command_index; +} FtpCommandInfo; + typedef struct FTPTransaction_ { /** id of this tx, starting at 0 */ uint64_t tx_id; @@ -62,7 +67,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? */ diff --git a/src/output-json-ftp.c b/src/output-json-ftp.c index 14232bdfe393..f8a2bd25b639 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)) { + SCLogNotice("Unable to fetch info for FTP command code %d [index %d]", + tx->command_descriptor.command_code, tx->command_descriptor.command_index); + } + } 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 {