/src/suricata8/rust/src/pop3/pop3.rs
Line | Count | Source |
1 | | /* Copyright (C) 2025 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | // Author: Alex Savage <exlavasage@gmail.com> |
19 | | |
20 | | //! POP3 parser |
21 | | |
22 | | use crate::applayer::*; |
23 | | use crate::conf::{conf_get, get_memval}; |
24 | | use crate::core::{ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP, sc_app_layer_parser_trigger_raw_stream_inspection}; |
25 | | use crate::flow::Flow; |
26 | | use crate::direction; |
27 | | use std; |
28 | | use std::collections::VecDeque; |
29 | | use std::ffi::CString; |
30 | | use std::os::raw::{c_char, c_int, c_void}; |
31 | | use suricata_sys::sys::{ |
32 | | AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled, |
33 | | SCAppLayerParserRegisterLogger, SCAppLayerParserSetStreamDepth, SCAppLayerParserStateIssetFlag, |
34 | | SCAppLayerProtoDetectConfProtoDetectionEnabled, SCAppLayerRequestProtocolTLSUpgrade, |
35 | | }; |
36 | | |
37 | | use sawp::error::Error as SawpError; |
38 | | use sawp::error::ErrorKind as SawpErrorKind; |
39 | | use sawp::parser::Direction; |
40 | | use sawp::parser::Parse; |
41 | | use sawp::probe::Probe; |
42 | | use sawp::probe::Status; |
43 | | use sawp_pop3::{self, Command, ErrorFlag, Flag, Flags, InnerMessage, Response}; |
44 | | |
45 | | static mut POP3_MAX_TX: usize = 256; |
46 | | |
47 | | pub(super) static mut ALPROTO_POP3: AppProto = ALPROTO_UNKNOWN; |
48 | | const POP3_PARSER: sawp_pop3::POP3 = sawp_pop3::POP3 {}; |
49 | | |
50 | | #[derive(AppLayerEvent)] |
51 | | enum POP3Event { |
52 | | TooManyTransactions, |
53 | | /// Command + space + argument + CRLF must not exceed 255 octets (RFC 2449) |
54 | | RequestTooLong, |
55 | | /// Number of arguments doesn't match the command |
56 | | IncorrectArgumentCount, |
57 | | /// Correct command format, but unknown value |
58 | | UnknownCommand, |
59 | | /// First line of server response + CRLF must not exceed 512 octets (RFC 2449) |
60 | | ResponseTooLong, |
61 | | } |
62 | | |
63 | | impl From<ErrorFlag> for POP3Event { |
64 | 10.3k | fn from(flag: ErrorFlag) -> Self { |
65 | 10.3k | match flag { |
66 | 1.71k | ErrorFlag::CommandTooLong => POP3Event::RequestTooLong, |
67 | 0 | ErrorFlag::IncorrectArgumentNum => POP3Event::IncorrectArgumentCount, |
68 | 8.60k | ErrorFlag::UnknownKeyword => POP3Event::UnknownCommand, |
69 | 0 | ErrorFlag::ResponseTooLong => POP3Event::ResponseTooLong, |
70 | | } |
71 | 10.3k | } |
72 | | } |
73 | | |
74 | | pub struct POP3Transaction { |
75 | | tx_id: u64, |
76 | | pub request: Option<Command>, |
77 | | pub response: Option<Response>, |
78 | | complete: bool, |
79 | | |
80 | | tx_data: AppLayerTxData, |
81 | | } |
82 | | |
83 | | impl POP3Transaction { |
84 | 8.60k | pub fn new(tx_id: u64) -> POP3Transaction { |
85 | 8.60k | Self { |
86 | 8.60k | tx_id, |
87 | 8.60k | request: None, |
88 | 8.60k | response: None, |
89 | 8.60k | complete: false, |
90 | 8.60k | tx_data: AppLayerTxData::new(), |
91 | 8.60k | } |
92 | 8.60k | } |
93 | | |
94 | 8.60k | fn error_flags_to_events(&mut self, flags: Flags<ErrorFlag>) { |
95 | 10.3k | for val in ErrorFlag::ITEMS |
96 | 8.60k | .iter() |
97 | 34.4k | .filter(move |&flag| flags.contains(*flag)) |
98 | 10.3k | { |
99 | 10.3k | self.tx_data.set_event(POP3Event::from(*val) as u8); |
100 | 10.3k | } |
101 | 8.60k | } |
102 | | } |
103 | | |
104 | | impl Transaction for POP3Transaction { |
105 | 88.8k | fn id(&self) -> u64 { |
106 | 88.8k | self.tx_id |
107 | 88.8k | } |
108 | | } |
109 | | |
110 | | #[derive(Default)] |
111 | | pub struct POP3State { |
112 | | state_data: AppLayerStateData, |
113 | | tx_id: u64, |
114 | | transactions: VecDeque<POP3Transaction>, |
115 | | request_gap: bool, |
116 | | response_gap: bool, |
117 | | retr_data: u32, |
118 | | auth_data_next: bool, |
119 | | } |
120 | | |
121 | | impl State<POP3Transaction> for POP3State { |
122 | 29.9k | fn get_transaction_count(&self) -> usize { |
123 | 29.9k | self.transactions.len() |
124 | 29.9k | } |
125 | | |
126 | 58.1k | fn get_transaction_by_index(&self, index: usize) -> Option<&POP3Transaction> { |
127 | 58.1k | self.transactions.get(index) |
128 | 58.1k | } |
129 | | } |
130 | | |
131 | | use nom7::{ |
132 | | bytes::streaming::{tag, take_until, take_while}, |
133 | | IResult, |
134 | | AsChar, |
135 | | }; |
136 | | |
137 | | #[inline] |
138 | 0 | fn is_base64_char(b: u8) -> bool { |
139 | 0 | b.is_alphanum() || b"+/=".contains(&b) |
140 | 0 | } |
141 | | |
142 | 0 | fn parse_base64_message(input: &[u8]) -> IResult<&[u8], &[u8]> { |
143 | 0 | let (input, value) = take_while(is_base64_char)(input)?; |
144 | 0 | let (input, _) = tag("\r\n")(input)?; |
145 | 0 | return Ok((input, value)); |
146 | 0 | } |
147 | | |
148 | 91 | fn find_end_of_header(input: &[u8]) -> IResult<&[u8], &[u8]> { |
149 | 91 | let (input, value) = take_until("\r\n")(input)?; |
150 | 30 | let (input, _) = tag("\r\n")(input)?; |
151 | 30 | return Ok((input, value)); |
152 | 91 | } |
153 | | |
154 | 0 | fn find_end_of_message(input: &[u8]) -> IResult<&[u8], &[u8]> { |
155 | 0 | let (input, value) = take_until(".\r\n")(input)?; |
156 | 0 | let (input, _) = tag(".\r\n")(input)?; |
157 | 0 | return Ok((input, value)); |
158 | 0 | } |
159 | | |
160 | 0 | fn parse_response_header_with_octets(input: &[u8]) -> IResult<&[u8], u32> { |
161 | 0 | let (input, size) = nom7::combinator::map_res( |
162 | 0 | nom7::bytes::complete::take_while_m_n(1, 10, |b: u8| b.is_ascii_digit()), |
163 | | std::str::from_utf8, |
164 | 0 | )(input)?; |
165 | 0 | let size: u32 = size.parse::<u32>().unwrap_or_default(); |
166 | | SCLogDebug!("size {}", size); |
167 | 0 | return Ok((input, size)); |
168 | 0 | } |
169 | | |
170 | | impl POP3State { |
171 | 399 | pub fn new() -> Self { |
172 | 399 | Default::default() |
173 | 399 | } |
174 | | |
175 | | // Free a transaction by ID. |
176 | 774 | fn free_tx(&mut self, tx_id: u64) { |
177 | 774 | if let Some(index) = self.transactions.iter().position(|tx| tx.id() == tx_id + 1) { |
178 | 774 | self.transactions.remove(index); |
179 | 774 | } |
180 | 774 | } |
181 | | |
182 | 0 | pub fn get_tx(&self, tx_id: u64) -> Option<&POP3Transaction> { |
183 | 0 | self.transactions.iter().find(|tx| tx.tx_id == tx_id + 1) |
184 | 0 | } |
185 | | |
186 | 0 | pub fn get_tx_mut(&mut self, tx_id: u64) -> Option<&mut POP3Transaction> { |
187 | 0 | self.transactions |
188 | 0 | .iter_mut() |
189 | 0 | .find(|tx| tx.tx_id == tx_id + 1) |
190 | 0 | } |
191 | | |
192 | 8.61k | fn new_tx(&mut self) -> Option<POP3Transaction> { |
193 | 8.61k | if self.transactions.len() > unsafe { POP3_MAX_TX } { |
194 | 3.61k | for tx_old in &mut self.transactions { |
195 | 3.59k | if !tx_old.complete { |
196 | 3.59k | tx_old.tx_data.updated_tc = true; |
197 | 3.59k | tx_old.tx_data.updated_ts = true; |
198 | 3.59k | tx_old.complete = true; |
199 | 3.59k | tx_old |
200 | 3.59k | .tx_data |
201 | 3.59k | .set_event(POP3Event::TooManyTransactions as u8); |
202 | 3.59k | } |
203 | | } |
204 | 14 | return None; |
205 | 8.60k | } |
206 | | |
207 | 8.60k | self.tx_id += 1; |
208 | 8.60k | Some(POP3Transaction::new(self.tx_id)) |
209 | 8.61k | } |
210 | | |
211 | 101 | fn find_request(&mut self) -> Option<&mut POP3Transaction> { |
212 | 101 | self.transactions |
213 | 101 | .iter_mut() |
214 | 101 | .find(|tx| tx.response.is_none()) |
215 | 101 | } |
216 | | |
217 | 1.05k | fn parse_request(&mut self, flow: *mut Flow, input: &[u8]) -> AppLayerResult { |
218 | | // We're not interested in empty requests. |
219 | 1.05k | if input.is_empty() { |
220 | 0 | return AppLayerResult::ok(); |
221 | 1.05k | } |
222 | | |
223 | | // If there was gap, check we can sync up again. |
224 | 1.05k | if self.request_gap { |
225 | | unsafe { |
226 | 0 | if probe(input, Direction::ToServer) != ALPROTO_POP3 { |
227 | | // The parser now needs to decide what to do as we are not in sync. |
228 | | // For this pop3, we'll just try again next time. |
229 | 0 | return AppLayerResult::ok(); |
230 | 0 | } |
231 | | } |
232 | | |
233 | | // It looks like we're in sync with a message header, clear gap |
234 | | // state and keep parsing. |
235 | 0 | self.request_gap = false; |
236 | 1.05k | } |
237 | | |
238 | 1.05k | let mut start = input; |
239 | 9.65k | while !start.is_empty() { |
240 | | // sawp_pop3 can't handle the AUTH blob "command", so |
241 | | // handle that here. |
242 | 8.85k | if self.auth_data_next { |
243 | 0 | match parse_base64_message(start) { |
244 | 0 | Ok((rem, _base64_blob)) => { |
245 | | SCLogDebug!("AUTH SASL blob {:?}", _base64_blob); |
246 | 0 | self.auth_data_next = false; |
247 | | |
248 | 0 | if let Some(mut tx) = self.new_tx() { |
249 | 0 | SCLogDebug!("tx created"); |
250 | 0 | let keyword = sawp_pop3::Keyword::Unknown("<SASL DATA>".to_string()); |
251 | 0 | tx.request = Some(Command { |
252 | 0 | keyword, |
253 | 0 | args: Vec::new(), |
254 | 0 | }); |
255 | 0 | self.transactions.push_back(tx); |
256 | 0 | sc_app_layer_parser_trigger_raw_stream_inspection( |
257 | 0 | flow, |
258 | 0 | direction::Direction::ToServer as i32, |
259 | 0 | ); |
260 | 0 | } |
261 | | |
262 | 0 | start = rem; |
263 | 0 | continue; |
264 | | } |
265 | 0 | Err(nom7::Err::Incomplete(needed)) => { |
266 | 0 | if let nom7::Needed::Size(n) = needed { |
267 | | SCLogDebug!("needed {}", n); |
268 | 0 | let consumed = input.len() - start.len(); |
269 | 0 | let needed = start.len() + n.get(); |
270 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
271 | 0 | } |
272 | 0 | return AppLayerResult::err(); |
273 | | } |
274 | 0 | Err(_e) => { |
275 | | SCLogDebug!("AUTH base64 parse failure {:?}", _e); |
276 | 0 | return AppLayerResult::err(); |
277 | | } |
278 | | } |
279 | 8.85k | } |
280 | 8.85k | match POP3_PARSER.parse(start, Direction::ToServer) { |
281 | 8.61k | Ok((rem, Some(msg))) => { |
282 | 8.61k | if let InnerMessage::Command(command) = msg.inner { |
283 | 8.61k | let mut tx = match self.new_tx() { |
284 | 8.60k | Some(tx) => tx, |
285 | 14 | None => return AppLayerResult::err(), |
286 | | }; |
287 | | SCLogDebug!("tx created"); |
288 | | |
289 | 8.60k | tx.error_flags_to_events(msg.error_flags); |
290 | 8.60k | tx.request = Some(command); |
291 | 8.60k | self.transactions.push_back(tx); |
292 | 8.60k | sc_app_layer_parser_trigger_raw_stream_inspection(flow, direction::Direction::ToServer as i32); |
293 | 0 | } |
294 | | |
295 | | SCLogDebug!("request done"); |
296 | 8.60k | start = rem; |
297 | | } |
298 | 0 | Ok((rem, None)) => { |
299 | | // Not enough data. This parser doesn't give us a good indication |
300 | | // of how much data is missing so just ask for one more byte so |
301 | | // parse is called as soon as more data is received. |
302 | | |
303 | 0 | let consumed = input.len() - rem.len(); |
304 | 0 | let needed = rem.len() + 1; |
305 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
306 | | } |
307 | | Err(SawpError { |
308 | 0 | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)), |
309 | | }) => { |
310 | 0 | let consumed = input.len() - start.len(); |
311 | 0 | let needed = start.len() + needed.get(); |
312 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
313 | | } |
314 | | Err(SawpError { |
315 | | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown), |
316 | | }) => { |
317 | 0 | let consumed = input.len() - start.len(); |
318 | 0 | let needed = start.len() + 1; |
319 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
320 | | } |
321 | 236 | Err(_e) => { |
322 | | SCLogDebug!("request error {:?}", _e); |
323 | 236 | return AppLayerResult::err(); |
324 | | } |
325 | | } |
326 | | } |
327 | | |
328 | | // Input was fully consumed. |
329 | 808 | return AppLayerResult::ok(); |
330 | 1.05k | } |
331 | | |
332 | 93 | fn parse_response(&mut self, input: &[u8], flow: *mut Flow) -> AppLayerResult { |
333 | | // skip RETR data |
334 | 93 | let mut start = if self.retr_data > 0 { |
335 | | SCLogDebug!("input {} retr_data {}", input.len(), self.retr_data); |
336 | 0 | if input.len() >= self.retr_data as usize { |
337 | 0 | let input = &input[self.retr_data as usize..]; |
338 | 0 | self.retr_data = 0; |
339 | 0 | if let Some(tx) = self.find_request() { |
340 | 0 | tx.complete = true; |
341 | 0 | } |
342 | 0 | input |
343 | | } else { |
344 | 0 | self.retr_data -= input.len() as u32; |
345 | 0 | &[] |
346 | | } |
347 | | } else { |
348 | 93 | input |
349 | | }; |
350 | | // We're not interested in empty responses. |
351 | 93 | if start.is_empty() { |
352 | 0 | return AppLayerResult::ok(); |
353 | 93 | } |
354 | | SCLogDebug!("input {} retr_data {}", start.len(), self.retr_data); |
355 | | |
356 | 93 | if self.response_gap { |
357 | | unsafe { |
358 | 0 | if probe(start, Direction::ToClient) != ALPROTO_POP3 { |
359 | | // The parser now needs to decide what to do as we are not in sync. |
360 | | // For this pop3, we'll just try again next time. |
361 | 0 | return AppLayerResult::ok(); |
362 | 0 | } |
363 | | } |
364 | | |
365 | | // It looks like we're in sync with a message header, clear gap |
366 | | // state and keep parsing. |
367 | 0 | self.response_gap = false; |
368 | 93 | } |
369 | 103 | while !start.is_empty() { |
370 | | // empty server challenge is not handled by sawp-pop3. Simply skip past it. |
371 | 101 | if start.starts_with(b"+ \r\n") { |
372 | | SCLogDebug!("empty server challenge"); |
373 | 10 | if let Some(tx) = self.find_request() { |
374 | 7 | tx.response = Some(Response { |
375 | 7 | status: sawp_pop3::Status::OK, |
376 | 7 | header: Vec::new(), |
377 | 7 | data: Vec::new(), |
378 | 7 | }); |
379 | 7 | tx.complete = true; |
380 | | SCLogDebug!( |
381 | | "found tx request: {:?} response: {:?}", |
382 | | tx.request, |
383 | | tx.response |
384 | | ); |
385 | | |
386 | 7 | if matches!( |
387 | 7 | tx.request.as_ref().map(|r| &r.keyword), |
388 | | Some(sawp_pop3::Keyword::AUTH) |
389 | 0 | ) { |
390 | 0 | SCLogDebug!("empty server challenge follows AUTH"); |
391 | 0 | self.auth_data_next = true; |
392 | 7 | } |
393 | 3 | } |
394 | 10 | start = &start[4..]; |
395 | 10 | continue; |
396 | 91 | } |
397 | | // since sawp-pop3 won't tell us if a command needs a multiline response, and if |
398 | | // it saw the full multiline response, we have to check for it here. |
399 | 91 | let (mut multiline, is_retr) = if let Some(tx) = self.find_request() { |
400 | 61 | let command = tx.request.as_ref().unwrap(); |
401 | | SCLogDebug!("command {:?}", command); |
402 | 61 | match command.keyword { |
403 | | sawp_pop3::Keyword::LIST|sawp_pop3::Keyword::UIDL => { |
404 | | // LIST and UIDL are in single line mode if they |
405 | | // have a argument |
406 | 0 | if !command.args.is_empty() { |
407 | 0 | (false, false) |
408 | | } else { |
409 | 0 | (true, false) |
410 | | } |
411 | | } |
412 | 0 | sawp_pop3::Keyword::TOP => (true, false), |
413 | 0 | sawp_pop3::Keyword::RETR => (true, true), |
414 | 61 | _ => (false, false), |
415 | | } |
416 | | } else { |
417 | 30 | (false, false) |
418 | | }; |
419 | | SCLogDebug!("multiline {}", multiline); |
420 | | |
421 | 91 | match find_end_of_header(start) { |
422 | 30 | Ok((body, header)) => { |
423 | | SCLogDebug!("end of header found"); |
424 | | |
425 | | // get the RETR octet size so we don't FP on the RETR data |
426 | | // when looking for the end of message marker |
427 | 30 | if is_retr && !header.is_empty() && header.starts_with(b"+OK ") && header.ends_with(b"octets") { |
428 | 0 | let header_args = &header[4..]; |
429 | | SCLogDebug!("octets line"); |
430 | | |
431 | 0 | let size = match parse_response_header_with_octets(header_args) { |
432 | 0 | Ok((_, size)) => size, |
433 | 0 | Err(_e) => { |
434 | | SCLogDebug!("header parsing error! {:?}", _e); |
435 | 0 | 0 |
436 | | } |
437 | | }; |
438 | | |
439 | 0 | let retr_octets_requested = size; |
440 | 0 | let retr_octets_processed = if body.len() < size as usize { |
441 | | SCLogDebug!("incomplete RETR data: {} < {}", body.len(), size); |
442 | 0 | body.len() as u32 |
443 | | } else { |
444 | 0 | retr_octets_requested |
445 | | }; |
446 | 0 | self.retr_data = retr_octets_requested - retr_octets_processed; |
447 | 0 | start = &body[retr_octets_processed as usize..]; |
448 | | |
449 | | // multiline no longer applies for errors |
450 | 30 | } else if header.starts_with(b"-ERR") { |
451 | 1 | multiline = false; |
452 | 29 | } |
453 | | } |
454 | | // partial header, lets return incomplete |
455 | 61 | Err(nom7::Err::Incomplete(needed)) => { |
456 | 61 | if let nom7::Needed::Size(n) = needed { |
457 | | SCLogDebug!("needed {}", n); |
458 | 0 | let consumed = input.len() - start.len(); |
459 | 0 | let needed = start.len() + n.get(); |
460 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
461 | 61 | } |
462 | 61 | return AppLayerResult::err(); |
463 | | } |
464 | | _ => { |
465 | 0 | return AppLayerResult::err(); |
466 | | } |
467 | | } |
468 | | |
469 | | // sawp-pop3 doesn't give us enough info about multiline parsing, so |
470 | | // look for the end of message marker ourselves. Except for the RETR |
471 | | // data, which can get too large, we just return incomplete until we |
472 | | // have all data. |
473 | 30 | if multiline { |
474 | 0 | match find_end_of_message(start) { |
475 | 0 | Ok((rem, _eom)) => { |
476 | | SCLogDebug!("end of multiline message found: message size {}", start.len() - rem.len()); |
477 | | |
478 | | // for RETR, search for the final multiline end marker to skip past it |
479 | 0 | if is_retr && self.retr_data == 0 { |
480 | | SCLogDebug!("command is RETR, all data processed, skip past EOM marker"); |
481 | 0 | start = rem; |
482 | | SCLogDebug!("start len {}", start.len()); |
483 | | |
484 | | // mark as complete |
485 | 0 | if let Some(tx) = self.find_request() { |
486 | 0 | tx.complete = true; |
487 | 0 | } |
488 | 0 | } |
489 | | } |
490 | | _ => { |
491 | | SCLogDebug!("no multiline response, consider incomplete"); |
492 | 0 | let consumed = input.len() - start.len(); |
493 | 0 | let needed = start.len() + 1; |
494 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
495 | | } |
496 | | } |
497 | 30 | } |
498 | | // we may have consumed all data by now |
499 | 30 | if start.is_empty() { |
500 | 0 | return AppLayerResult::ok(); |
501 | 30 | } |
502 | | |
503 | 30 | match POP3_PARSER.parse(start, Direction::ToClient) { |
504 | 0 | Ok((rem, Some(msg))) => { |
505 | | SCLogDebug!("msg {:?} start:{} rem:{}", msg, start.len(), rem.len()); |
506 | 0 | if let InnerMessage::Response(mut response) = msg.inner { |
507 | 0 | let mut auth_ok = false; |
508 | 0 | let tx = if let Some(tx) = self.find_request() { |
509 | 0 | tx |
510 | | } else { |
511 | | // Server sends banner before any requests |
512 | 0 | let tx = match self.new_tx() { |
513 | 0 | Some(tx) => tx, |
514 | 0 | None => return AppLayerResult::err(), |
515 | | }; |
516 | | |
517 | 0 | let tx_id = tx.id(); |
518 | 0 | self.transactions.push_back(tx); |
519 | 0 | self.get_tx_mut(tx_id - 1).unwrap() |
520 | | }; |
521 | | |
522 | 0 | tx.error_flags_to_events(msg.error_flags); |
523 | 0 | tx.complete = true; |
524 | 0 | sc_app_layer_parser_trigger_raw_stream_inspection(flow, direction::Direction::ToClient as i32); |
525 | 0 | if response.status == sawp_pop3::Status::OK { |
526 | 0 | if let Some(command) = &tx.request { |
527 | | SCLogDebug!("command {:?}", command); |
528 | 0 | match &command.keyword { |
529 | 0 | sawp_pop3::Keyword::STLS => { |
530 | 0 | unsafe { |
531 | 0 | SCAppLayerRequestProtocolTLSUpgrade(flow); |
532 | 0 | }; |
533 | 0 | } |
534 | 0 | sawp_pop3::Keyword::RETR => { |
535 | 0 | // Don't hold onto the whole email body |
536 | 0 |
|
537 | 0 | // TODO: pass off to mime parser |
538 | 0 | response.data.clear(); |
539 | 0 | } |
540 | 0 | sawp_pop3::Keyword::AUTH => { |
541 | 0 | SCLogDebug!("OK on AUTH, expect base64 blob"); |
542 | 0 | auth_ok = true; |
543 | 0 | } |
544 | 0 | _ => {} |
545 | | } |
546 | 0 | } |
547 | 0 | } |
548 | 0 | tx.response = Some(response); |
549 | 0 | if auth_ok { |
550 | 0 | self.auth_data_next = true; |
551 | 0 | } |
552 | 0 | } |
553 | 0 | start = rem; |
554 | | SCLogDebug!("updated: start:{} rem:{}", start.len(), rem.len()); |
555 | | } |
556 | 0 | Ok((rem, None)) => { |
557 | | // Not enough data. This parser doesn't give us a good indication |
558 | | // of how much data is missing so just ask for one more byte so |
559 | | // parse is called as soon as more data is received. |
560 | | |
561 | 0 | let consumed = input.len() - rem.len(); |
562 | 0 | let needed = rem.len() + 1; |
563 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
564 | | } |
565 | | Err(SawpError { |
566 | 0 | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)), |
567 | | }) => { |
568 | 0 | let consumed = input.len() - start.len(); |
569 | 0 | let needed = start.len() + needed.get(); |
570 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
571 | | } |
572 | | Err(SawpError { |
573 | | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown), |
574 | | }) => { |
575 | 0 | let consumed = input.len() - start.len(); |
576 | 0 | let needed = start.len() + 1; |
577 | 0 | return AppLayerResult::incomplete(consumed as u32, needed as u32); |
578 | | } |
579 | 30 | Err(_e) => { |
580 | | SCLogDebug!("response error {:?}", _e); |
581 | 30 | return AppLayerResult::err(); |
582 | | } |
583 | | } |
584 | | } |
585 | | |
586 | | // All input was fully consumed. |
587 | 2 | return AppLayerResult::ok(); |
588 | 93 | } |
589 | | |
590 | 0 | fn on_request_gap(&mut self) { |
591 | 0 | self.request_gap = true; |
592 | 0 | } |
593 | | |
594 | 0 | fn on_response_gap(&mut self) { |
595 | 0 | self.response_gap = true; |
596 | 0 | } |
597 | | } |
598 | | |
599 | | /// Reasonably need at least 5 bytes to determine |
600 | | /// 3 bytes for short commands like 'TOP' or response '+OK' |
601 | | /// and 2 bytes for the CRLF. |
602 | | static MIN_PROBE_LEN: u32 = 5; |
603 | | |
604 | | /// Probe for a command or response |
605 | 6.82k | fn probe(input: &[u8], direction: Direction) -> AppProto { |
606 | 6.82k | match POP3_PARSER.probe(input, direction) { |
607 | 768 | Status::Recognized => unsafe { ALPROTO_POP3 }, |
608 | 0 | Status::Incomplete => ALPROTO_UNKNOWN, |
609 | 6.06k | Status::Unrecognized => ALPROTO_FAILED, |
610 | | } |
611 | 6.82k | } |
612 | | |
613 | | // C exports. |
614 | | |
615 | | /// C entry point for a probing parser. |
616 | 3.95k | unsafe extern "C" fn pop3_probe_tc( |
617 | 3.95k | _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, |
618 | 3.95k | ) -> AppProto { |
619 | 3.95k | if input_len < MIN_PROBE_LEN { |
620 | 2.34k | ALPROTO_UNKNOWN |
621 | | } else { |
622 | 1.60k | let slice = build_slice!(input, input_len as usize); |
623 | 1.60k | probe(slice, Direction::ToClient) |
624 | | } |
625 | 3.95k | } |
626 | | |
627 | 5.55k | unsafe extern "C" fn pop3_probe_ts( |
628 | 5.55k | _flow: *const Flow, _direction: u8, input: *const u8, input_len: u32, _rdir: *mut u8, |
629 | 5.55k | ) -> AppProto { |
630 | 5.55k | if input_len < MIN_PROBE_LEN { |
631 | 331 | ALPROTO_UNKNOWN |
632 | | } else { |
633 | 5.22k | let slice = build_slice!(input, input_len as usize); |
634 | 5.22k | probe(slice, Direction::ToServer) |
635 | | } |
636 | 5.55k | } |
637 | | |
638 | 399 | extern "C" fn pop3_state_new(_orig_state: *mut c_void, _orig_proto: AppProto) -> *mut c_void { |
639 | 399 | let state = POP3State::new(); |
640 | 399 | let boxed = Box::new(state); |
641 | 399 | return Box::into_raw(boxed) as *mut c_void; |
642 | 399 | } |
643 | | |
644 | 399 | unsafe extern "C" fn pop3_state_free(state: *mut c_void) { |
645 | 399 | std::mem::drop(Box::from_raw(state as *mut POP3State)); |
646 | 399 | } |
647 | | |
648 | 774 | unsafe extern "C" fn pop3_state_tx_free(state: *mut c_void, tx_id: u64) { |
649 | 774 | let state = cast_pointer!(state, POP3State); |
650 | 774 | state.free_tx(tx_id); |
651 | 774 | } |
652 | | |
653 | 1.08k | unsafe extern "C" fn pop3_parse_request( |
654 | 1.08k | flow: *mut Flow, state: *mut c_void, pstate: *mut AppLayerParserState, stream_slice: StreamSlice, |
655 | 1.08k | _data: *const c_void, |
656 | 1.08k | ) -> AppLayerResult { |
657 | 1.08k | let eof = SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0; |
658 | | |
659 | 1.08k | if eof { |
660 | | // If needed, handle EOF, or pass it into the parser. |
661 | 24 | return AppLayerResult::ok(); |
662 | 1.05k | } |
663 | | |
664 | 1.05k | let state = cast_pointer!(state, POP3State); |
665 | | |
666 | 1.05k | if stream_slice.is_gap() { |
667 | | // Here we have a gap signaled by the input being null, but a greater |
668 | | // than 0 input_len which provides the size of the gap. |
669 | 0 | state.on_request_gap(); |
670 | 0 | AppLayerResult::ok() |
671 | | } else { |
672 | 1.05k | let buf = stream_slice.as_slice(); |
673 | 1.05k | state.parse_request(flow, buf) |
674 | | } |
675 | 1.08k | } |
676 | | |
677 | 93 | unsafe extern "C" fn pop3_parse_response( |
678 | 93 | flow: *mut Flow, state: *mut c_void, pstate: *mut AppLayerParserState, stream_slice: StreamSlice, |
679 | 93 | _data: *const c_void, |
680 | 93 | ) -> AppLayerResult { |
681 | 93 | let _eof = SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0; |
682 | 93 | let state = cast_pointer!(state, POP3State); |
683 | | |
684 | 93 | if stream_slice.is_gap() { |
685 | | // Here we have a gap signaled by the input being null, but a greater |
686 | | // than 0 input_len which provides the size of the gap. |
687 | 0 | state.on_response_gap(); |
688 | 0 | AppLayerResult::ok() |
689 | | } else { |
690 | 93 | let buf = stream_slice.as_slice(); |
691 | 93 | state.parse_response(buf, flow) |
692 | | } |
693 | 93 | } |
694 | | |
695 | 0 | unsafe extern "C" fn pop3_state_get_tx(state: *mut c_void, tx_id: u64) -> *mut c_void { |
696 | 0 | let state = cast_pointer!(state, POP3State); |
697 | 0 | match state.get_tx(tx_id) { |
698 | 0 | Some(tx) => { |
699 | 0 | return tx as *const _ as *mut _; |
700 | | } |
701 | | None => { |
702 | 0 | return std::ptr::null_mut(); |
703 | | } |
704 | | } |
705 | 0 | } |
706 | | |
707 | 3.05k | unsafe extern "C" fn pop3_state_get_tx_count(state: *mut c_void) -> u64 { |
708 | 3.05k | let state = cast_pointer!(state, POP3State); |
709 | 3.05k | return state.tx_id; |
710 | 3.05k | } |
711 | | |
712 | 30.6k | unsafe extern "C" fn pop3_tx_get_alstate_progress(tx: *mut c_void, direction: u8) -> c_int { |
713 | 30.6k | let tx = cast_pointer!(tx, POP3Transaction); |
714 | 30.6k | if direction == u8::from(direction::Direction::ToServer) { |
715 | 1.00k | (tx.request.is_some() || tx.complete) as c_int |
716 | | } else { |
717 | 29.6k | (tx.response.is_some() || tx.complete) as c_int |
718 | | } |
719 | 30.6k | } |
720 | | |
721 | | export_tx_data_get!(pop3_get_tx_data, POP3Transaction); |
722 | | export_state_data_get!(pop3_get_state_data, POP3State); |
723 | | |
724 | | // Parser name as a C style string. |
725 | | const PARSER_NAME: &[u8] = b"pop3\0"; |
726 | | |
727 | | #[no_mangle] |
728 | 6 | pub unsafe extern "C" fn SCRegisterPop3Parser() { |
729 | 6 | let default_port = CString::new("[110]").unwrap(); |
730 | 6 | let parser = RustParser { |
731 | 6 | name: PARSER_NAME.as_ptr() as *const c_char, |
732 | 6 | default_port: default_port.as_ptr(), |
733 | 6 | ipproto: IPPROTO_TCP, |
734 | 6 | probe_ts: Some(pop3_probe_ts), |
735 | 6 | probe_tc: Some(pop3_probe_tc), |
736 | 6 | min_depth: 0, |
737 | 6 | max_depth: 16, |
738 | 6 | state_new: pop3_state_new, |
739 | 6 | state_free: pop3_state_free, |
740 | 6 | tx_free: pop3_state_tx_free, |
741 | 6 | parse_ts: pop3_parse_request, |
742 | 6 | parse_tc: pop3_parse_response, |
743 | 6 | get_tx_count: pop3_state_get_tx_count, |
744 | 6 | get_tx: pop3_state_get_tx, |
745 | 6 | tx_comp_st_ts: 1, |
746 | 6 | tx_comp_st_tc: 1, |
747 | 6 | tx_get_progress: pop3_tx_get_alstate_progress, |
748 | 6 | get_eventinfo: Some(POP3Event::get_event_info), |
749 | 6 | get_eventinfo_byid: Some(POP3Event::get_event_info_by_id), |
750 | 6 | localstorage_new: None, |
751 | 6 | localstorage_free: None, |
752 | 6 | get_tx_files: None, |
753 | 6 | get_tx_iterator: Some(state_get_tx_iterator::<POP3State, POP3Transaction>), |
754 | 6 | get_tx_data: pop3_get_tx_data, |
755 | 6 | get_state_data: pop3_get_state_data, |
756 | 6 | apply_tx_config: None, |
757 | 6 | flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS, |
758 | 6 | get_frame_id_by_name: None, |
759 | 6 | get_frame_name_by_id: None, |
760 | 6 | get_state_id_by_name: None, |
761 | 6 | get_state_name_by_id: None, |
762 | 6 | }; |
763 | | |
764 | 6 | let ip_proto_str = CString::new("tcp").unwrap(); |
765 | | |
766 | 6 | if SCAppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
767 | 6 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
768 | 6 | ALPROTO_POP3 = alproto; |
769 | 6 | if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
770 | 6 | let _ = AppLayerRegisterParser(&parser, alproto); |
771 | 6 | } |
772 | 6 | let retval = conf_get("app-layer.protocols.pop3.stream-depth"); |
773 | 6 | if let Some(val) = retval { |
774 | 0 | match get_memval(val) { |
775 | 0 | Ok(retval) => { |
776 | 0 | let stream_depth = retval as u32; |
777 | 0 | SCAppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_POP3, stream_depth); |
778 | 0 | } |
779 | | Err(_) => { |
780 | 0 | SCLogError!("Invalid depth value"); |
781 | | } |
782 | | } |
783 | 6 | } |
784 | 6 | if let Some(val) = conf_get("app-layer.protocols.pop3.max-tx") { |
785 | 0 | if let Ok(v) = val.parse::<usize>() { |
786 | 0 | POP3_MAX_TX = v; |
787 | 0 | } else { |
788 | 0 | SCLogError!("Invalid value for pop3.max-tx"); |
789 | | } |
790 | 6 | } |
791 | 6 | SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_POP3); |
792 | | SCLogDebug!("Rust pop3 parser registered."); |
793 | 0 | } else { |
794 | 0 | SCLogDebug!("Protocol detector and parser disabled for POP3."); |
795 | 0 | } |
796 | 6 | } |