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