postfix_log_parser/components/
pickup.rs1use crate::components::ComponentParser;
32use crate::events::pickup::PickupEvent;
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 MAIL_PICKUP_REGEX: Regex = Regex::new(
47 r"^([A-F0-9]+): uid=(\d+) from=(.+)$"
48 ).unwrap();
49}
50
51pub struct PickupParser;
52
53impl PickupParser {
54 pub fn new() -> Self {
55 Self
56 }
57
58 fn parse_config_override_warning(&self, message: &str) -> Option<PickupEvent> {
61 if let Some(captures) = CONFIG_OVERRIDE_WARNING_REGEX.captures(message) {
62 let file_path = captures.get(1)?.as_str().to_string();
63 let line_number = captures.get(2)?.as_str().parse::<u32>().ok()?;
64 let parameter_name = captures.get(3)?.as_str().to_string();
65 let parameter_value = captures.get(4)?.as_str().to_string();
66
67 Some(PickupEvent::config_override_warning(
68 Utc::now(),
69 None,
70 file_path,
71 line_number,
72 parameter_name,
73 parameter_value,
74 ))
75 } else {
76 None
77 }
78 }
79
80 fn parse_mail_pickup(&self, message: &str) -> Option<PickupEvent> {
82 if let Some(captures) = MAIL_PICKUP_REGEX.captures(message) {
83 let queue_id = captures.get(1)?.as_str().to_string();
84 let uid = captures.get(2)?.as_str().parse::<u32>().ok()?;
85 let sender = captures.get(3)?.as_str().to_string();
86
87 Some(PickupEvent::mail_pickup(
88 Utc::now(),
89 None,
90 queue_id,
91 uid,
92 sender,
93 ))
94 } else {
95 None
96 }
97 }
98}
99
100impl ComponentParser for PickupParser {
101 fn parse(&self, message: &str) -> Result<ComponentEvent, crate::error::ParseError> {
102 let clean_message = message.trim();
103
104 if let Some(event) = self.parse_config_override_warning(clean_message) {
106 return Ok(ComponentEvent::Pickup(event));
107 }
108
109 if let Some(event) = self.parse_mail_pickup(clean_message) {
111 return Ok(ComponentEvent::Pickup(event));
112 }
113
114 Err(crate::error::ParseError::ComponentParseError {
115 component: "pickup".to_string(),
116 reason: format!("Unable to parse message: {}", message),
117 })
118 }
119
120 fn component_name(&self) -> &'static str {
121 "pickup"
122 }
123}
124
125impl Default for PickupParser {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use crate::events::pickup::PickupEventType;
135
136 fn create_parser() -> PickupParser {
137 PickupParser::new()
138 }
139
140 #[test]
141 fn test_parse_config_override_warning() {
142 let parser = create_parser();
143
144 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";
145 let result = parser.parse_config_override_warning(message);
146
147 assert!(result.is_some());
148 let event = result.unwrap();
149
150 match &event.event_type {
151 PickupEventType::ConfigOverrideWarning {
152 file_path,
153 line_number,
154 parameter_name,
155 parameter_value,
156 } => {
157 assert_eq!(file_path, "/etc/postfix/main.cf");
158 assert_eq!(*line_number, 820);
159 assert_eq!(parameter_name, "smtpd_recipient_restrictions");
160 assert!(parameter_value.contains("check_client_access"));
161 }
162 _ => panic!("Expected ConfigOverrideWarning event type"),
163 }
164 }
165
166 #[test]
167 fn test_parse_mail_pickup() {
168 let parser = create_parser();
169
170 let message = "226751E20F00: uid=0 from=<root>";
171 let result = parser.parse_mail_pickup(message);
172
173 assert!(result.is_some());
174 let event = result.unwrap();
175
176 match &event.event_type {
177 PickupEventType::MailPickup {
178 queue_id,
179 uid,
180 sender,
181 } => {
182 assert_eq!(queue_id, "226751E20F00");
183 assert_eq!(*uid, 0);
184 assert_eq!(sender, "<root>");
185 }
186 _ => panic!("Expected MailPickup event type"),
187 }
188 }
189
190 #[test]
191 fn test_component_parser_parse_config_override() {
192 let parser = create_parser();
193
194 let message = "/etc/postfix/main.cf, line 806: overriding earlier entry: smtpd_client_message_rate_limit=0";
195 let result = parser.parse(message);
196
197 assert!(result.is_ok());
198 match result.unwrap() {
199 ComponentEvent::Pickup(event) => {
200 assert_eq!(event.severity(), "warning");
201 assert_eq!(
202 event.parameter_name(),
203 Some("smtpd_client_message_rate_limit")
204 );
205 assert_eq!(event.queue_id(), None);
206 assert_eq!(event.sender(), None);
207 }
208 _ => panic!("Expected Pickup ComponentEvent"),
209 }
210 }
211
212 #[test]
213 fn test_component_parser_parse_mail_pickup() {
214 let parser = create_parser();
215
216 let message = "226751E20F00: uid=0 from=<root>";
217 let result = parser.parse(message);
218
219 assert!(result.is_ok());
220 match result.unwrap() {
221 ComponentEvent::Pickup(event) => {
222 assert_eq!(event.severity(), "info");
223 assert_eq!(event.parameter_name(), None);
224 assert_eq!(event.queue_id(), Some("226751E20F00"));
225 assert_eq!(event.sender(), Some("<root>"));
226 assert_eq!(event.uid(), Some(0));
227 }
228 _ => panic!("Expected Pickup ComponentEvent"),
229 }
230 }
231
232 #[test]
233 fn test_component_parser_parse_invalid() {
234 let parser = create_parser();
235
236 let message = "some unknown message format";
237 let result = parser.parse(message);
238
239 assert!(result.is_err());
240 match result.unwrap_err() {
241 crate::error::ParseError::ComponentParseError { component, .. } => {
242 assert_eq!(component, "pickup");
243 }
244 _ => panic!("Expected ComponentParseError"),
245 }
246 }
247
248 #[test]
249 fn test_component_name() {
250 let parser = create_parser();
251 assert_eq!(parser.component_name(), "pickup");
252 }
253
254 #[test]
255 fn test_parse_real_log_samples() {
256 let parser = create_parser();
257
258 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";
260 let result1 = parser.parse(message1);
261 assert!(result1.is_ok());
262
263 let message2 = "226751E20F00: uid=0 from=<root>";
265 let result2 = parser.parse(message2);
266 assert!(result2.is_ok());
267
268 let message3 = "/etc/postfix/main.cf, line 826: overriding earlier entry: smtpd_discard_ehlo_keywords=silent-discard,dsn,etrn";
270 let result3 = parser.parse(message3);
271 assert!(result3.is_ok());
272
273 match result3.unwrap() {
274 ComponentEvent::Pickup(event) => {
275 assert_eq!(event.parameter_name(), Some("smtpd_discard_ehlo_keywords"));
276 }
277 _ => panic!("Expected Pickup ComponentEvent"),
278 }
279 }
280}