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

Smtp server detection 1125 v2.7 #11493

Closed
Closed
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
61 changes: 61 additions & 0 deletions rust/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,68 @@
use std::ffi::CStr;
use std::os::raw::c_char;

use nom7::bytes::complete::take_while1;
use nom7::character::complete::char;
use nom7::character::{is_alphabetic, is_alphanumeric};
use nom7::combinator::verify;
use nom7::multi::many1_count;
use nom7::IResult;

#[no_mangle]
pub unsafe extern "C" fn rs_check_utf8(val: *const c_char) -> bool {
CStr::from_ptr(val).to_str().is_ok()
}

fn is_alphanumeric_or_hyphen(chr: u8) -> bool {
return is_alphanumeric(chr) || chr == b'-';
}

fn parse_domain_label(i: &[u8]) -> IResult<&[u8], ()> {
let (i, _) = verify(take_while1(is_alphanumeric_or_hyphen), |x: &[u8]| {
is_alphabetic(x[0]) && x[x.len() - 1] != b'-'
})(i)?;
return Ok((i, ()));
}

fn parse_subdomain(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = parse_domain_label(input)?;
let (input, _) = char('.')(input)?;
return Ok((input, ()));
}

fn parse_domain(input: &[u8]) -> IResult<&[u8], ()> {
let (input, _) = many1_count(parse_subdomain)(input)?;
let (input, _) = parse_domain_label(input)?;
return Ok((input, ()));
}

#[no_mangle]
pub unsafe extern "C" fn SCValidateDomain(input: *const u8, in_len: u32) -> u32 {
let islice = build_slice!(input, in_len as usize);
if let Ok((rem, _)) = parse_domain(islice) {
return (islice.len() - rem.len()) as u32;
}
return 0;
}

#[cfg(test)]
mod tests {

use super::*;

#[test]
fn test_parse_domain() {
let buf0: &[u8] = "a-1.oisf.net more".as_bytes();
let (rem, _) = parse_domain(buf0).unwrap();
// And we should have 5 bytes left.
assert_eq!(rem.len(), 5);
let buf1: &[u8] = "justatext".as_bytes();
assert!(parse_domain(buf1).is_err());
let buf1: &[u8] = "1.com".as_bytes();
assert!(parse_domain(buf1).is_err());
let buf1: &[u8] = "a-.com".as_bytes();
assert!(parse_domain(buf1).is_err());
let buf1: &[u8] = "a(x)y.com".as_bytes();
assert!(parse_domain(buf1).is_err());
}
}
35 changes: 34 additions & 1 deletion src/app-layer-ftp.c
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,31 @@ static AppProto FTPUserProbingParser(
return ALPROTO_FTP;
}

static AppProto FTPServerProbingParser(
Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
{
// another check for minimum length
if (len < 5) {
return ALPROTO_UNKNOWN;
}
// begins by 220
if (input[0] != '2' || input[1] != '2' || input[2] != '0') {
return ALPROTO_FAILED;
}
// followed by space or hypen
if (input[3] != ' ' && input[3] != '-') {
return ALPROTO_FAILED;
}
if (f->alproto_ts == ALPROTO_FTP || (f->todstbytecnt > 4 && f->alproto_ts == ALPROTO_UNKNOWN)) {
// only validates FTP if client side was FTP
// or if client side is unknown despite having received bytes
if (memchr(input + 4, '\n', len - 4) != NULL) {
return ALPROTO_FTP;
}
}
return ALPROTO_UNKNOWN;
}

static int FTPRegisterPatternsForProtocolDetection(void)
{
if (AppLayerProtoDetectPMRegisterPatternCI(
Expand All @@ -987,7 +1012,15 @@ static int FTPRegisterPatternsForProtocolDetection(void)
IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER) < 0) {
return -1;
}

// Only check FTP on known ports as the banner has nothing special beyond
// the response code shared with SMTP.
if (!AppLayerProtoDetectPPParseConfPorts(
"tcp", IPPROTO_TCP, "ftp", ALPROTO_FTP, 0, 5, NULL, FTPServerProbingParser)) {
// STREAM_TOSERVER here means use 21 as flow destination port
// and NULL, FTPServerProbingParser means use probing parser to client
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "21", ALPROTO_FTP, 0, 5, STREAM_TOSERVER, NULL,
FTPServerProbingParser);
}
return 0;
}

Expand Down
79 changes: 76 additions & 3 deletions src/app-layer-smtp.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,17 +215,30 @@ enum SMTPCode {
SMTP_REPLY_334,
SMTP_REPLY_354,

SMTP_REPLY_401, // Unauthorized
SMTP_REPLY_402, // Command not implemented
SMTP_REPLY_421,
SMTP_REPLY_435, // Your account has not yet been verified
SMTP_REPLY_450,
SMTP_REPLY_451,
SMTP_REPLY_452,
SMTP_REPLY_454, // Temporary authentication failure
SMTP_REPLY_455,

SMTP_REPLY_500,
SMTP_REPLY_501,
SMTP_REPLY_502,
SMTP_REPLY_503,
SMTP_REPLY_504,
SMTP_REPLY_511, // Bad email address
SMTP_REPLY_521, // Server does not accept mail
SMTP_REPLY_522, // Recipient has exceeded mailbox limit
SMTP_REPLY_525, // User Account Disabled
SMTP_REPLY_530, // Authentication required
SMTP_REPLY_534, // Authentication mechanism is too weak
SMTP_REPLY_535, // Authentication credentials invalid
SMTP_REPLY_541, // No response from host
SMTP_REPLY_543, // Routing server failure. No available route
SMTP_REPLY_550,
SMTP_REPLY_551,
SMTP_REPLY_552,
Expand All @@ -234,7 +247,7 @@ enum SMTPCode {
SMTP_REPLY_555,
};

SCEnumCharMap smtp_reply_map[ ] = {
SCEnumCharMap smtp_reply_map[] = {
{ "211", SMTP_REPLY_211 },
{ "214", SMTP_REPLY_214 },
{ "220", SMTP_REPLY_220 },
Expand All @@ -247,24 +260,38 @@ SCEnumCharMap smtp_reply_map[ ] = {
{ "334", SMTP_REPLY_334 },
{ "354", SMTP_REPLY_354 },

{ "401", SMTP_REPLY_401 },
{ "402", SMTP_REPLY_402 },
{ "421", SMTP_REPLY_421 },
{ "435", SMTP_REPLY_435 },
{ "450", SMTP_REPLY_450 },
{ "451", SMTP_REPLY_451 },
{ "452", SMTP_REPLY_452 },
{ "454", SMTP_REPLY_454 },
// { "4.7.0", SMTP_REPLY_454 }, // rfc4954
{ "455", SMTP_REPLY_455 },

{ "500", SMTP_REPLY_500 },
{ "501", SMTP_REPLY_501 },
{ "502", SMTP_REPLY_502 },
{ "503", SMTP_REPLY_503 },
{ "504", SMTP_REPLY_504 },
{ "511", SMTP_REPLY_511 },
{ "521", SMTP_REPLY_521 },
{ "522", SMTP_REPLY_522 },
{ "525", SMTP_REPLY_525 },
{ "530", SMTP_REPLY_530 },
{ "534", SMTP_REPLY_534 },
{ "535", SMTP_REPLY_535 },
{ "541", SMTP_REPLY_541 },
{ "543", SMTP_REPLY_543 },
{ "550", SMTP_REPLY_550 },
{ "551", SMTP_REPLY_551 },
{ "552", SMTP_REPLY_552 },
{ "553", SMTP_REPLY_553 },
{ "554", SMTP_REPLY_554 },
{ "555", SMTP_REPLY_555 },
{ NULL, -1 },
{ NULL, -1 },
};

/* Create SMTP config structure */
Expand Down Expand Up @@ -1368,7 +1395,7 @@ static AppLayerResult SMTPParse(uint8_t direction, Flow *f, SMTPState *state,
AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC)))) {
SCReturnStruct(APP_LAYER_OK);
} else if (input_buf == NULL || input_len == 0) {
SCReturnStruct(APP_LAYER_ERROR);
SCReturnStruct(APP_LAYER_OK);
}

SMTPInput input = { .buf = input_buf, .len = input_len, .orig_len = input_len, .consumed = 0 };
Expand Down Expand Up @@ -1657,6 +1684,46 @@ static int SMTPStateGetEventInfoById(int event_id, const char **event_name,
return 0;
}

static AppProto SMTPServerProbingParser(
Flow *f, uint8_t direction, const uint8_t *input, uint32_t len, uint8_t *rdir)
{
// another check for minimum length
if (len < 5) {
return ALPROTO_UNKNOWN;
}
// begins by 220
if (input[0] != '2' || input[1] != '2' || input[2] != '0') {
return ALPROTO_FAILED;
}
// followed by space or hypen
if (input[3] != ' ' && input[3] != '-') {
return ALPROTO_FAILED;
}
// If client side is SMTP, do not validate domain
// so that server banner can be parsed first.
if (f->alproto_ts == ALPROTO_SMTP) {
if (memchr(input + 4, '\n', len - 4) != NULL) {
return ALPROTO_SMTP;
}
return ALPROTO_UNKNOWN;
}
AppProto r = ALPROTO_UNKNOWN;
if (f->todstbytecnt > 4 && f->alproto_ts == ALPROTO_UNKNOWN) {
// Only validates SMTP if client side is unknown
// despite having received bytes.
r = ALPROTO_SMTP;
}
uint32_t offset = SCValidateDomain(input + 4, len - 4);
if (offset == 0) {
return ALPROTO_FAILED;
}
if (r != ALPROTO_UNKNOWN && memchr(input + 4, '\n', len - 4) != NULL) {
return r;
}
// This should not go forever because of engine limiting probing parsers.
return ALPROTO_UNKNOWN;
}

static int SMTPRegisterPatternsForProtocolDetection(void)
{
if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_SMTP,
Expand All @@ -1674,6 +1741,12 @@ static int SMTPRegisterPatternsForProtocolDetection(void)
{
return -1;
}
if (!AppLayerProtoDetectPPParseConfPorts(
"tcp", IPPROTO_TCP, "smtp", ALPROTO_SMTP, 0, 5, NULL, SMTPServerProbingParser)) {
// STREAM_TOSERVER means here use 25 as flow destination port
AppLayerProtoDetectPPRegister(IPPROTO_TCP, "25,465", ALPROTO_SMTP, 0, 5, STREAM_TOSERVER,
NULL, SMTPServerProbingParser);
}

return 0;
}
Expand Down
Loading