diff --git a/rust/Cargo.lock.in b/rust/Cargo.lock.in index 7438c3646d..11895656c8 100644 --- a/rust/Cargo.lock.in +++ b/rust/Cargo.lock.in @@ -685,6 +685,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "humantime" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" + [[package]] name = "indexmap" version = "2.9.0" @@ -1522,6 +1528,7 @@ dependencies = [ "flate2", "hex", "hkdf", + "humantime", "ipsec-parser", "kerberos-parser", "lazy_static", diff --git a/rust/Cargo.toml.in b/rust/Cargo.toml.in index ddbce97b0c..a90063e804 100644 --- a/rust/Cargo.toml.in +++ b/rust/Cargo.toml.in @@ -76,6 +76,7 @@ lazy_static = "~1.5.0" base64 = "~0.22.1" bendy = { version = "~0.3.3", default-features = false } asn1-rs = { version = "~0.6.2" } +humantime = "~2.3.0" ldap-parser = { version = "~0.5.0" } hex = "~0.4.3" psl = "2" diff --git a/rust/src/util.rs b/rust/src/util.rs index 56b37ea853..a8ebe9b9e4 100644 --- a/rust/src/util.rs +++ b/rust/src/util.rs @@ -17,6 +17,7 @@ //! Utility module. +use std::borrow::Cow; use std::ffi::CStr; use std::os::raw::c_char; @@ -27,6 +28,8 @@ use nom7::combinator::verify; use nom7::multi::many1_count; use nom7::IResult; +use humantime::parse_duration; + #[no_mangle] pub unsafe extern "C" fn SCCheckUtf8(val: *const c_char) -> bool { CStr::from_ptr(val).to_str().is_ok() @@ -64,10 +67,56 @@ pub unsafe extern "C" fn SCValidateDomain(input: *const u8, in_len: u32) -> u32 return 0; } +/// Add 's' suffix if input is only digits, and convert to lowercase if needed. +fn duration_unit_normalize(input: &str) -> Cow<'_, str> { + if input.bytes().all(|b| b.is_ascii_digit()) { + let mut owned = String::with_capacity(input.len() + 1); + owned.push_str(input); + owned.push('s'); + return Cow::Owned(owned); + } + + if input.bytes().any(|b| b.is_ascii_uppercase()) { + Cow::Owned(input.to_ascii_lowercase()) + } else { + Cow::Borrowed(input) + } +} + +/// Reads a C string from `input`, parses it, and writes the result to `*res`. +/// Returns 0 on success (result written to *res), -1 otherwise. +#[no_mangle] +pub unsafe extern "C" fn SCParseTimeDuration(input: *const c_char, res: *mut u64) -> i32 { + if input.is_null() || res.is_null() { + return -1; + } + + let input_str = match CStr::from_ptr(input).to_str() { + Ok(s) => s, + Err(_) => return -1, + }; + + let trimmed = input_str.trim(); + if trimmed.is_empty() { + return -1; + } + + let normalized = duration_unit_normalize(trimmed); + match parse_duration(normalized.as_ref()) { + Ok(duration) => { + *res = duration.as_secs(); + 0 + } + Err(_) => -1, + } +} + #[cfg(test)] mod tests { use super::*; + use std::ffi::CString; + use std::ptr::{null, null_mut}; #[test] fn test_parse_domain() { @@ -84,4 +133,73 @@ mod tests { let buf1: &[u8] = "a(x)y.com".as_bytes(); assert!(parse_domain(buf1).is_err()); } + + #[test] + fn test_parse_time_valid() { + unsafe { + let mut v: u64 = 0; + + let s = CString::new("10").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 10); + + let s = CString::new("0").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 0); + + let s = CString::new("2H").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 7200); + + let s = CString::new("1 day").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 86400); + + let s = CString::new("1w").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 604800); + + let s = CString::new("1 week").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 604800); + + let s = CString::new("1y").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 31557600); + + let s = CString::new("1 year").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, 31557600); + + // max + let s = CString::new("18446744073709551615").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), 0); + assert_eq!(v, u64::MAX); + } + } + + #[test] + fn test_parse_time_duration_invalid() { + unsafe { + let mut v: u64 = 0; + let s = CString::new("10q").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), -1); + + let s = CString::new("abc").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), -1); + + let s = CString::new("-300s").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), -1); + + let s = CString::new("1h -600s").unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), -1); + + assert_eq!(SCParseTimeDuration(null(), &mut v), -1); + assert_eq!(SCParseTimeDuration(s.as_ptr(), null_mut()), -1); + + let overflow_years = (u64::MAX / 31557600) + 1; + let s = CString::new(format!("{}y", overflow_years)).unwrap(); + assert_eq!(SCParseTimeDuration(s.as_ptr(), &mut v), -1); + } + } } diff --git a/rust/sys/src/sys.rs b/rust/sys/src/sys.rs index 5a7effeace..1f1f8ec93b 100644 --- a/rust/sys/src/sys.rs +++ b/rust/sys/src/sys.rs @@ -478,6 +478,11 @@ extern "C" { name: *const ::std::os::raw::c_char, val: *mut f32, ) -> ::std::os::raw::c_int; } +extern "C" { + pub fn SCConfGetTime( + name: *const ::std::os::raw::c_char, val: *mut u64, + ) -> ::std::os::raw::c_int; +} extern "C" { pub fn SCConfSet( name: *const ::std::os::raw::c_char, val: *const ::std::os::raw::c_char,