Coverage Report

Created: 2026-11-12 06:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}