sentinel_modsec/operators/
mod.rs1mod traits;
4mod pattern;
5mod comparison;
6mod detection;
7mod validation;
8mod network;
9
10pub use traits::{Operator, OperatorResult};
11pub use pattern::{RxOperator, PmOperator};
12pub use comparison::{ContainsOperator, BeginsWithOperator, EndsWithOperator, StreqOperator};
13pub use comparison::{EqOperator, GtOperator, LtOperator, GeOperator, LeOperator};
14pub use detection::{DetectSqliOperator, DetectXssOperator};
15pub use validation::{ValidateUrlEncodingOperator, ValidateUtf8EncodingOperator};
16pub use network::IpMatchOperator;
17
18use crate::parser::{OperatorName, OperatorSpec};
19use crate::error::{Error, Result};
20use std::sync::Arc;
21
22pub type CompiledOperator = dyn Operator;
24
25pub fn create_operator(name: OperatorName, argument: &str) -> Result<Arc<dyn Operator>> {
28 let spec = OperatorSpec {
29 negated: false,
30 name,
31 argument: argument.to_string(),
32 };
33 compile_operator(&spec)
34}
35
36pub fn compile_operator(spec: &OperatorSpec) -> Result<Arc<dyn Operator>> {
38 let name = &spec.name;
39 let argument = &spec.argument;
40 match name {
41 OperatorName::Rx => Ok(Arc::new(RxOperator::new(argument)?)),
42 OperatorName::Pm | OperatorName::Pmf => Ok(Arc::new(PmOperator::new(argument)?)),
43 OperatorName::PmFromFile => Ok(Arc::new(PmOperator::from_file(argument)?)),
44 OperatorName::Contains => Ok(Arc::new(ContainsOperator::new(argument))),
45 OperatorName::BeginsWith => Ok(Arc::new(BeginsWithOperator::new(argument))),
46 OperatorName::EndsWith => Ok(Arc::new(EndsWithOperator::new(argument))),
47 OperatorName::StreQ => Ok(Arc::new(StreqOperator::new(argument))),
48 OperatorName::Eq => Ok(Arc::new(EqOperator::new(argument))),
49 OperatorName::Gt => Ok(Arc::new(GtOperator::new(argument))),
50 OperatorName::Lt => Ok(Arc::new(LtOperator::new(argument))),
51 OperatorName::Ge => Ok(Arc::new(GeOperator::new(argument))),
52 OperatorName::Le => Ok(Arc::new(LeOperator::new(argument))),
53 OperatorName::DetectSqli => Ok(Arc::new(DetectSqliOperator)),
54 OperatorName::DetectXss => Ok(Arc::new(DetectXssOperator)),
55 OperatorName::ValidateUrlEncoding => Ok(Arc::new(ValidateUrlEncodingOperator)),
56 OperatorName::ValidateUtf8Encoding => Ok(Arc::new(ValidateUtf8EncodingOperator)),
57 OperatorName::IpMatch | OperatorName::IpMatchF => Ok(Arc::new(IpMatchOperator::new(argument)?)),
58 OperatorName::IpMatchFromFile => Ok(Arc::new(IpMatchOperator::from_file(argument)?)),
59 OperatorName::NoMatch => Ok(Arc::new(NoMatchOperator)),
60 OperatorName::UnconditionalMatch => Ok(Arc::new(UnconditionalMatchOperator)),
61 OperatorName::ValidateByteRange => Ok(Arc::new(ValidateByteRangeOperator::new(argument))),
62 OperatorName::VerifyCc | OperatorName::VerifySsn | OperatorName::VerifyCpf => {
64 Ok(Arc::new(NoMatchOperator))
65 }
66 OperatorName::ValidateHash | OperatorName::ValidateDtd | OperatorName::ValidateSchema => {
67 Ok(Arc::new(NoMatchOperator))
68 }
69 OperatorName::Rbl | OperatorName::GeoLookup | OperatorName::GsbLookup => {
70 Ok(Arc::new(NoMatchOperator))
71 }
72 OperatorName::InspectFile | OperatorName::FuzzyHash | OperatorName::Rsub => {
73 Ok(Arc::new(NoMatchOperator))
74 }
75 OperatorName::ContainsWord => Ok(Arc::new(ContainsOperator::new(argument))),
76 OperatorName::Within => Ok(Arc::new(WithinOperator::new(argument))),
77 OperatorName::StrMatch => Ok(Arc::new(ContainsOperator::new(argument))),
78 OperatorName::Ne => Ok(Arc::new(NeOperator::new(argument))),
79 _ => Err(Error::UnknownOperator { name: format!("{:?}", name) }),
80 }
81}
82
83pub struct NoMatchOperator;
85
86impl Operator for NoMatchOperator {
87 fn execute(&self, _value: &str) -> OperatorResult {
88 OperatorResult::no_match()
89 }
90
91 fn name(&self) -> &'static str {
92 "noMatch"
93 }
94}
95
96pub struct UnconditionalMatchOperator;
98
99impl Operator for UnconditionalMatchOperator {
100 fn execute(&self, value: &str) -> OperatorResult {
101 OperatorResult::matched(value.to_string())
102 }
103
104 fn name(&self) -> &'static str {
105 "unconditionalMatch"
106 }
107}
108
109pub struct ValidateByteRangeOperator {
111 #[allow(dead_code)]
112 ranges: Vec<(u8, u8)>,
113}
114
115impl ValidateByteRangeOperator {
116 pub fn new(spec: &str) -> Self {
118 let mut ranges = Vec::new();
120 for part in spec.split(',') {
121 let part = part.trim();
122 if part.contains('-') {
123 let parts: Vec<&str> = part.split('-').collect();
124 if parts.len() == 2 {
125 if let (Ok(start), Ok(end)) = (parts[0].parse(), parts[1].parse()) {
126 ranges.push((start, end));
127 }
128 }
129 } else if let Ok(byte) = part.parse() {
130 ranges.push((byte, byte));
131 }
132 }
133 Self { ranges }
134 }
135}
136
137impl Operator for ValidateByteRangeOperator {
138 fn execute(&self, value: &str) -> OperatorResult {
139 for byte in value.bytes() {
141 let valid = self.ranges.iter().any(|(start, end)| byte >= *start && byte <= *end);
142 if !valid {
143 return OperatorResult::matched(format!("invalid byte: {}", byte));
145 }
146 }
147 OperatorResult::no_match()
148 }
149
150 fn name(&self) -> &'static str {
151 "validateByteRange"
152 }
153}
154
155pub struct WithinOperator {
157 values: Vec<String>,
158}
159
160impl WithinOperator {
161 pub fn new(values: &str) -> Self {
163 Self {
164 values: values.split_whitespace().map(|s| s.to_string()).collect(),
165 }
166 }
167}
168
169impl Operator for WithinOperator {
170 fn execute(&self, value: &str) -> OperatorResult {
171 if self.values.iter().any(|v| v == value) {
172 OperatorResult::matched(value.to_string())
173 } else {
174 OperatorResult::no_match()
175 }
176 }
177
178 fn name(&self) -> &'static str {
179 "within"
180 }
181}
182
183pub struct NeOperator {
185 expected: String,
186}
187
188impl NeOperator {
189 pub fn new(expected: &str) -> Self {
191 Self {
192 expected: expected.to_string(),
193 }
194 }
195}
196
197impl Operator for NeOperator {
198 fn execute(&self, value: &str) -> OperatorResult {
199 if let (Ok(a), Ok(b)) = (value.parse::<i64>(), self.expected.parse::<i64>()) {
201 if a != b {
202 return OperatorResult::matched(value.to_string());
203 }
204 } else if value != self.expected {
205 return OperatorResult::matched(value.to_string());
207 }
208 OperatorResult::no_match()
209 }
210
211 fn name(&self) -> &'static str {
212 "ne"
213 }
214}