]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
smtp: adds server side detection
authorPhilippe Antoine <pantoine@oisf.net>
Tue, 28 Jun 2022 19:34:24 +0000 (21:34 +0200)
committerVictor Julien <victor@inliniac.net>
Wed, 17 Jul 2024 04:13:39 +0000 (06:13 +0200)
Ticket: #1125

rust/src/util.rs
src/app-layer-smtp.c

index d7109464f7733f70b2921fca5914ca1558547bf0..d3f76e41eb7c6649d11b9276211d1e5ed89f41ab 100644 (file)
 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());
+    }
+}
index c03ff75cd070ac69eced70b1c595863fcf84112c..b645e77676ddd36993c39f20a76ac0c051757102 100644 (file)
@@ -1657,6 +1657,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,
@@ -1674,6 +1714,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", ALPROTO_SMTP, 0, 5, STREAM_TOSERVER, NULL,
+                SMTPServerProbingParser);
+    }
 
     return 0;
 }