postfix_log_parser/components/
trivial_rewrite.rs1use crate::components::ComponentParser;
32use crate::events::trivial_rewrite::TrivialRewriteEvent;
33use crate::events::ComponentEvent;
34use chrono::Utc;
35use lazy_static::lazy_static;
36use regex::Regex;
37
38lazy_static! {
39 static ref CONFIG_OVERRIDE_WARNING_REGEX: Regex = Regex::new(
42 r"^(.+?), line (\d+): overriding earlier entry: (.+?)=(.+)$"
43 ).unwrap();
44
45 static ref DOMAIN_CONFIG_WARNING_REGEX: Regex = Regex::new(
48 r"^do not list domain (.+?) in BOTH (.+?) and (.+?)$"
49 ).unwrap();
50}
51
52pub struct TrivialRewriteParser;
53
54impl TrivialRewriteParser {
55 pub fn new() -> Self {
56 Self
57 }
58
59 fn parse_config_override_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
62 if let Some(captures) = CONFIG_OVERRIDE_WARNING_REGEX.captures(message) {
63 let file_path = captures.get(1)?.as_str().to_string();
64 let line_number: u32 = captures.get(2)?.as_str().parse().ok()?;
65 let parameter_name = captures.get(3)?.as_str().to_string();
66 let parameter_value = captures.get(4)?.as_str().to_string();
67
68 Some(TrivialRewriteEvent::config_override_warning(
69 Utc::now(),
70 None,
71 file_path,
72 line_number,
73 parameter_name,
74 parameter_value,
75 ))
76 } else {
77 None
78 }
79 }
80
81 fn parse_domain_config_warning(&self, message: &str) -> Option<TrivialRewriteEvent> {
84 if let Some(captures) = DOMAIN_CONFIG_WARNING_REGEX.captures(message) {
85 let domain = captures.get(1)?.as_str().to_string();
86 let domain_list1 = captures.get(2)?.as_str().to_string();
87 let domain_list2 = captures.get(3)?.as_str().to_string();
88 let full_message = message.to_string(); Some(TrivialRewriteEvent::domain_config_warning(
91 Utc::now(),
92 None,
93 domain,
94 domain_list1,
95 domain_list2,
96 full_message,
97 ))
98 } else {
99 None
100 }
101 }
102}
103
104impl ComponentParser for TrivialRewriteParser {
105 fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
106 let clean_message = message.trim();
107
108 if let Some(event) = self.parse_config_override_warning(clean_message) {
110 return Ok(ComponentEvent::TrivialRewrite(event));
111 }
112
113 if let Some(event) = self.parse_domain_config_warning(clean_message) {
115 return Ok(ComponentEvent::TrivialRewrite(event));
116 }
117
118 Err(crate::error::ParseError::ComponentParseError {
119 component: "trivial-rewrite".to_string(),
120 reason: format!("Unable to parse message: {}", message),
121 })
122 }
123
124 fn component_name(&self) -> &'static str {
125 "trivial-rewrite"
126 }
127}
128
129impl Default for TrivialRewriteParser {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use crate::events::trivial_rewrite::TrivialRewriteEventType;
139
140 fn create_parser() -> TrivialRewriteParser {
141 TrivialRewriteParser::new()
142 }
143
144 #[test]
145 fn test_parse_config_override_warning() {
146 let parser = create_parser();
147
148 let message = "/etc/postfix/main.cf, line 820: overriding earlier entry: smtpd_recipient_restrictions=check_client_access pcre:/etc/postfix/filter_trusted,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,pcre:/etc/postfix/filter_default";
149 let result = parser.parse_config_override_warning(message);
150
151 assert!(result.is_some());
152 let event = result.unwrap();
153
154 match &event.event_type {
155 TrivialRewriteEventType::ConfigOverrideWarning {
156 file_path,
157 line_number,
158 parameter_name,
159 parameter_value,
160 } => {
161 assert_eq!(file_path, "/etc/postfix/main.cf");
162 assert_eq!(*line_number, 820);
163 assert_eq!(parameter_name, "smtpd_recipient_restrictions");
164 assert!(parameter_value.contains("check_client_access"));
165 }
166 _ => panic!("Expected ConfigOverrideWarning event type"),
167 }
168 }
169
170 #[test]
171 fn test_parse_domain_config_warning() {
172 let parser = create_parser();
173
174 let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
175 let result = parser.parse_domain_config_warning(message);
176
177 assert!(result.is_some());
178 let event = result.unwrap();
179
180 match &event.event_type {
181 TrivialRewriteEventType::DomainConfigWarning {
182 domain,
183 domain_list1,
184 domain_list2,
185 message: full_message,
186 } => {
187 assert_eq!(domain, "qq.com");
188 assert_eq!(domain_list1, "virtual_alias_domains");
189 assert_eq!(domain_list2, "relay_domains");
190 assert_eq!(
191 full_message,
192 "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains"
193 );
194 }
195 _ => panic!("Expected DomainConfigWarning event type"),
196 }
197 }
198
199 #[test]
200 fn test_component_parser_parse_config_override() {
201 let parser = create_parser();
202
203 let message = "/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0";
204 let result = parser.parse(message);
205
206 assert!(result.is_ok());
207 match result.unwrap() {
208 ComponentEvent::TrivialRewrite(event) => {
209 assert_eq!(event.severity(), "warning");
210 assert_eq!(
211 event.parameter_name(),
212 Some("smtpd_client_message_rate_limit")
213 );
214 }
215 _ => panic!("Expected TrivialRewrite ComponentEvent"),
216 }
217 }
218
219 #[test]
220 fn test_component_parser_parse_domain_config() {
221 let parser = create_parser();
222
223 let message = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
224 let result = parser.parse(message);
225
226 assert!(result.is_ok());
227 match result.unwrap() {
228 ComponentEvent::TrivialRewrite(event) => {
229 assert_eq!(event.severity(), "warning");
230 assert_eq!(event.domain(), Some("qq.com"));
231 }
232 _ => panic!("Expected TrivialRewrite ComponentEvent"),
233 }
234 }
235
236 #[test]
237 fn test_component_parser_parse_invalid() {
238 let parser = create_parser();
239
240 let message = "some unknown message format";
241 let result = parser.parse(message);
242
243 assert!(result.is_err());
244 match result.unwrap_err() {
245 crate::error::ParseError::ComponentParseError { component, .. } => {
246 assert_eq!(component, "trivial-rewrite");
247 }
248 _ => panic!("Expected ComponentParseError"),
249 }
250 }
251
252 #[test]
253 fn test_component_name() {
254 let parser = create_parser();
255 assert_eq!(parser.component_name(), "trivial-rewrite");
256 }
257
258 #[test]
259 fn test_parse_real_log_samples() {
260 let parser = create_parser();
261
262 let message1 = "/etc/postfix/main.cf, line 819: overriding earlier entry: smtpd_recipient_restrictions=check_client_access pcre:/etc/postfix/filter_trusted,permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,pcre:/etc/postfix/filter_default";
264 let result1 = parser.parse(message1);
265 assert!(result1.is_ok());
266
267 let message2 = "do not list domain qq.com in BOTH virtual_alias_domains and relay_domains";
269 let result2 = parser.parse(message2);
270 assert!(result2.is_ok());
271
272 let message3 = "/etc/postfix/main.cf, line 826: overriding earlier entry: smtpd_discard_ehlo_keywords=silent-discard,dsn,etrn";
274 let result3 = parser.parse(message3);
275 assert!(result3.is_ok());
276
277 match result3.unwrap() {
278 ComponentEvent::TrivialRewrite(event) => {
279 assert_eq!(event.parameter_name(), Some("smtpd_discard_ehlo_keywords"));
280 }
281 _ => panic!("Expected TrivialRewrite ComponentEvent"),
282 }
283 }
284}