Skip to content

Commit

Permalink
ftp: Convert command table to rust
Browse files Browse the repository at this point in the history
Issue: 4082

Convert the FTP command table to rust. The command table is loaded into
MPM to efficiently return the observed command. Formerly in C, the
conversion to Rust incorporates the FTP command info into the FTP TX
instead of incorporating it as a reference.
  • Loading branch information
jlucovsky committed Aug 23, 2024
1 parent 4f4caa3 commit 3f0010d
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 107 deletions.
113 changes: 98 additions & 15 deletions rust/src/ftp/ftp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<FtpCommand> = 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 {
Expand Down Expand Up @@ -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;
Expand Down
120 changes: 42 additions & 78 deletions src/app-layer-ftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
7 changes: 6 additions & 1 deletion src/app-layer-ftp.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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? */
Expand Down
Loading

0 comments on commit 3f0010d

Please sign in to comment.