--------
The ``requires`` keyword allows a rule to require specific Suricata
-features to be enabled, or the Suricata version to match an
-expression. Rules that do not meet the requirements will by ignored,
-and Suricata will not treat them as errors.
+features to be enabled, specific keywords to be available, or the
+Suricata version to match an expression. Rules that do not meet the
+requirements will be ignored, and Suricata will not treat them as
+errors.
Requirements that follow the valid format of ``<keyword>
<expression>`` but are not known to Suricata are allowed for future
The format is::
- requires: feature geoip, version >= 7.0.0
+ requires: feature geoip, version >= 7.0.0, keyword foobar
To require multiple features, the feature sub-keyword must be
specified multiple times::
requires: version >= 7.0.4 < 8 | >= 8.0.3
-to express that a rules requires version 7.0.4 or greater, but less
+to express that a rule requires version 7.0.4 or greater, but less
than 8, **OR** greater than or equal to 8.0.3. Which could be useful
if a keyword wasn't added until 7.0.4 and the 8.0.3 patch releases, as
it would not exist in 8.0.1.
/// An unknown requirement was provided.
UnknownRequirement(String),
+
+ /// Suricata does not have support for a required keyword.
+ MissingKeyword(String),
}
impl RequiresError {
Self::MultipleVersions => "Version may only be specified once\0",
Self::Utf8Error => "Requires expression is not valid UTF-8\0",
Self::UnknownRequirement(_) => "Unknown requirements\0",
+ Self::MissingKeyword(_) => "Suricata missing a required keyword\0",
};
msg.as_ptr() as *const c_char
}
#[derive(Debug, Default, Eq, PartialEq)]
struct Requires {
+ /// Features required to be enabled.
pub features: Vec<String>,
+ /// Rule keywords required to exist.
+ pub keywords: Vec<String>,
+
/// The version expression.
///
/// - All of the inner most must evaluate to true.
parse_version_expression(value).map_err(|_| RequiresError::BadRequires)?;
requires.version = versions;
}
+ "keyword" => {
+ requires.keywords.push(value.trim().to_string());
+ }
_ => {
// Unknown keyword, allow by warn in case we extend
// this in the future.
}
}
+ for keyword in &requires.keywords {
+ if !crate::feature::has_keyword(keyword) {
+ return Err(RequiresError::MissingKeyword(keyword.to_string()));
+ }
+ }
+
Ok(())
}
requires,
Requires {
features: vec![],
+ keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
requires,
Requires {
features: vec![],
+ keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
requires,
Requires {
features: vec!["output::file-store".to_string()],
+ keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
requires,
Requires {
features: vec!["geoip".to_string()],
+ keywords: vec![],
version: vec![vec![
RuleRequireVersion {
op: VersionCompareOp::Gte,
requires,
Requires {
features: vec!["true_lua".to_string()],
+ keywords: vec![],
version: vec![vec![RuleRequireVersion {
op: VersionCompareOp::Gte,
version: SuricataVersion {
#[test]
fn test_requires_keyword() {
let requires = parse_requires("keyword true_bar").unwrap();
+ assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_ok());
+
+ let requires = parse_requires("keyword bar").unwrap();
assert!(check_requires(&requires, &SuricataVersion::new(8, 0, 0)).is_err());
}
}
pub fn requires(feature: &str) -> bool {
return feature.starts_with("true");
}
+
+ /// Check for a keyword returning true if found.
+ ///
+ /// This a "mock" variant of `has_keyword` that will return true
+ /// for any keyword starting with string `true`, and false for
+ /// anything else.
+ pub fn has_keyword(keyword: &str) -> bool {
+ return keyword.starts_with("true");
+ }
}
#[cfg(not(test))]
extern "C" {
fn RequiresFeature(feature: *const c_char) -> bool;
+ fn SigTableHasKeyword(keyword: *const c_char) -> bool;
}
/// Check for a feature returning true if found.
false
}
}
+
+ pub fn has_keyword(keyword: &str) -> bool {
+ if let Ok(keyword) = CString::new(keyword) {
+ unsafe { SigTableHasKeyword(keyword.as_ptr()) }
+ } else {
+ false
+ }
+ }
}
#[cfg(not(test))]