diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 17:39:49 +0000 |
commit | a0aa2307322cd47bbf416810ac0292925e03be87 (patch) | |
tree | 37076262a026c4b48c8a0e84f44ff9187556ca35 /rust/src/ftp | |
parent | Initial commit. (diff) | |
download | suricata-a0aa2307322cd47bbf416810ac0292925e03be87.tar.xz suricata-a0aa2307322cd47bbf416810ac0292925e03be87.zip |
Adding upstream version 1:7.0.3.upstream/1%7.0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'rust/src/ftp')
-rw-r--r-- | rust/src/ftp/event.rs | 50 | ||||
-rw-r--r-- | rust/src/ftp/mod.rs | 242 |
2 files changed, 292 insertions, 0 deletions
diff --git a/rust/src/ftp/event.rs b/rust/src/ftp/event.rs new file mode 100644 index 0000000..04cc9e3 --- /dev/null +++ b/rust/src/ftp/event.rs @@ -0,0 +1,50 @@ +/* Copyright (C) 2023 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 crate::core::AppLayerEventType; +use std::os::raw::{c_char, c_int}; + +#[derive(Debug, PartialEq, Eq, AppLayerEvent)] +#[repr(C)] +pub enum FtpEvent { + #[name("request_command_too_long")] + FtpEventRequestCommandTooLong, + #[name("response_command_too_long")] + FtpEventResponseCommandTooLong, +} + +/// Wrapper around the Rust generic function for get_event_info. +/// +/// # Safety +/// Unsafe as called from C. +#[no_mangle] +pub unsafe extern "C" fn ftp_get_event_info( + event_name: *const c_char, event_id: *mut c_int, event_type: *mut AppLayerEventType, +) -> c_int { + crate::applayer::get_event_info::<FtpEvent>(event_name, event_id, event_type) +} + +/// Wrapper around the Rust generic function for get_event_info_by_id. +/// +/// # Safety +/// Unsafe as called from C. +#[no_mangle] +pub unsafe extern "C" fn ftp_get_event_info_by_id( + event_id: c_int, event_name: *mut *const c_char, event_type: *mut AppLayerEventType, +) -> c_int { + crate::applayer::get_event_info_by_id::<FtpEvent>(event_id, event_name, event_type) as c_int +} diff --git a/rust/src/ftp/mod.rs b/rust/src/ftp/mod.rs new file mode 100644 index 0000000..3839c96 --- /dev/null +++ b/rust/src/ftp/mod.rs @@ -0,0 +1,242 @@ +/* Copyright (C) 2017 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 parser and application layer module. + +use nom7::bytes::complete::{tag, take_until}; +use nom7::character::complete::{digit1, multispace0}; +use nom7::combinator::{complete, map_res, opt, verify}; +use nom7::sequence::{delimited, tuple}; +use nom7::{Err, IResult}; +use std; +use std::str; +use std::str::FromStr; + +pub mod event; + +// We transform an integer string into a i64, ignoring surrounding whitespaces +// We look for a digit suite, and try to convert it. +// If either str::from_utf8 or FromStr::from_str fail, +// we fallback to the parens parser defined above +fn getu16(i: &[u8]) -> IResult<&[u8], u16> { + map_res( + map_res(delimited(multispace0, digit1, multispace0), str::from_utf8), + FromStr::from_str, + )(i) +} + +fn parse_u16(i: &[u8]) -> IResult<&[u8], u16> { + map_res(map_res(digit1, str::from_utf8), u16::from_str)(i) +} + +// PORT 192,168,0,13,234,10 +pub fn ftp_active_port(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("PORT")(i)?; + let (i, _) = delimited(multispace0, digit1, multispace0)(i)?; + let (i, _) = tuple(( + tag(","), + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + ))(i)?; + let (i, part1) = verify(parse_u16, |&v| v <= std::u8::MAX as u16)(i)?; + let (i, _) = tag(",")(i)?; + let (i, part2) = verify(parse_u16, |&v| v <= std::u8::MAX as u16)(i)?; + Ok((i, part1 * 256 + part2)) +} + +// 227 Entering Passive Mode (212,27,32,66,221,243). +pub fn ftp_pasv_response(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("227")(i)?; + let (i, _) = take_until("(")(i)?; + let (i, _) = tag("(")(i)?; + let (i, _) = tuple(( + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + digit1, + tag(","), + ))(i)?; + let (i, part1) = verify(getu16, |&v| v <= std::u8::MAX as u16)(i)?; + let (i, _) = tag(",")(i)?; + let (i, part2) = verify(getu16, |&v| v <= std::u8::MAX as u16)(i)?; + // may also be completed by a final point + let (i, _) = tag(")")(i)?; + let (i, _) = opt(complete(tag(".")))(i)?; + Ok((i, part1 * 256 + part2)) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_active_port(input: *const u8, len: u32) -> u16 { + let buf = build_slice!(input, len as usize); + match ftp_active_port(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("port incomplete: '{:?}'", buf); + } + Err(_) => { + SCLogDebug!("port error on '{:?}'", buf); + } + } + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_pasv_response(input: *const u8, len: u32) -> u16 { + let buf = std::slice::from_raw_parts(input, len as usize); + match ftp_pasv_response(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("pasv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("pasv error on '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} + +// 229 Entering Extended Passive Mode (|||48758|). +pub fn ftp_epsv_response(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("229")(i)?; + let (i, _) = take_until("|||")(i)?; + let (i, _) = tag("|||")(i)?; + let (i, port) = getu16(i)?; + let (i, _) = tag("|)")(i)?; + let (i, _) = opt(complete(tag(".")))(i)?; + Ok((i, port)) +} + +// EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|41813| +pub fn ftp_active_eprt(i: &[u8]) -> IResult<&[u8], u16> { + let (i, _) = tag("EPRT")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, _) = take_until("|")(i)?; + let (i, _) = tag("|")(i)?; + let (i, port) = getu16(i)?; + let (i, _) = tag("|")(i)?; + Ok((i, port)) +} + +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_active_eprt(input: *const u8, len: u32) -> u16 { + let buf = build_slice!(input, len as usize); + match ftp_active_eprt(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("eprt incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} +#[no_mangle] +pub unsafe extern "C" fn rs_ftp_epsv_response(input: *const u8, len: u32) -> u16 { + let buf = std::slice::from_raw_parts(input, len as usize); + match ftp_epsv_response(buf) { + Ok((_, dport)) => { + return dport; + } + Err(Err::Incomplete(_)) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + Err(_) => { + SCLogDebug!("epsv incomplete: '{:?}'", String::from_utf8_lossy(buf)); + } + } + return 0; +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_pasv_response_valid() { + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,221,243).".as_bytes()); + assert_eq!(port, Ok((&b""[..], 56819))); + let port_notdot = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,221,243)".as_bytes()); + assert_eq!(port_notdot, Ok((&b""[..], 56819))); + + let port_epsv_dot = + ftp_epsv_response("229 Entering Extended Passive Mode (|||48758|).".as_bytes()); + assert_eq!(port_epsv_dot, Ok((&b""[..], 48758))); + let port_epsv_nodot = + ftp_epsv_response("229 Entering Extended Passive Mode (|||48758|)".as_bytes()); + assert_eq!(port_epsv_nodot, Ok((&b""[..], 48758))); + } + + #[test] + fn test_active_eprt_valid() { + let port = + ftp_active_eprt("EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|41813|".as_bytes()); + assert_eq!(port, Ok((&b""[..], 41813))); + } + + #[test] + fn test_active_port_valid() { + let port = ftp_active_port("PORT 192,168,0,13,234,10".as_bytes()); + assert_eq!(port, Ok((&b""[..], 59914))); + } + + // A port that is too large for a u16. + #[test] + fn test_pasv_response_too_large() { + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,257,243).".as_bytes()); + assert!(port.is_err()); + + let port = + ftp_pasv_response("227 Entering Passive Mode (212,27,32,66,255,65535).".as_bytes()); + assert!(port.is_err()); + } + + #[test] + fn test_active_eprt_too_large() { + let port = + ftp_active_eprt("EPRT |2|2a01:e34:ee97:b130:8c3e:45ea:5ac6:e301|81813|".as_bytes()); + assert!(port.is_err()); + } + + #[test] + fn test_active_port_too_large() { + let port = ftp_active_port("PORT 212,27,32,66,257,243".as_bytes()); + assert!(port.is_err()); + + let port = ftp_active_port("PORT 212,27,32,66,255,65535".as_bytes()); + assert!(port.is_err()); + } +} |