Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ftp: begin conversion process to Rust #12657

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/userguide/upgrade.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <value> for ftp.memcap` when this occurs.

Removals
~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions rust/cbindgen.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ include = [
"QuicState",
"QuicTransaction",
"FtpEvent",
"FtpRequestCommand",
"FtpStateValues",
"FtpDataStateValues",
"SCSigTableElmt",
"SCTransformTableElmt",
"DataRepType",
Expand Down
89 changes: 89 additions & 0 deletions rust/src/ftp/constant.rs
Original file line number Diff line number Diff line change
@@ -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,
}
245 changes: 245 additions & 0 deletions rust/src/ftp/ftp.rs
Original file line number Diff line number Diff line change
@@ -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<FtpCommand> = 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::<u32>() {
*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);
}
}
4 changes: 3 additions & 1 deletion rust/src/ftp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down
Loading
Loading