Add comprehensive POP3 examples and documentation
Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>
copilot-swe-agent[bot] committed
Oct 13, 2025 at 17:18 UTC
76063dddf7aa0c800c4867f7d1aa45789eb30d80
7 files changed
+3383
-2
README.md
+108
-2
@@ -1,6 +1,6 @@
1
-# SMTP Server
1
+# Mail Server
2
3
-A basic SMTP server implementation in Rust with TLS/STARTTLS support.
3
+A mail server implementation in Rust with support for SMTP, POP3, and IMAP protocols with TLS/STARTTLS support.
4
5
## Features
6
@@ -45,6 +45,112 @@ echo -e "EHLO test.com\r\nMAIL FROM: <sender@example.com>\r\nRCPT TO: <recipient
45
cargo build --release
46
```
47
48
+## POP3 Protocol Support
49
+
50
+This repository includes comprehensive examples and documentation for the POP3 (Post Office Protocol version 3) implementation.
51
+
52
+### POP3 Features
53
+
54
+- ✅ USER/PASS authentication
55
+- ✅ STAT command for mailbox statistics
56
+- ✅ LIST command for message listing
57
+- ✅ RETR command for message retrieval
58
+- ✅ DELE command for message deletion
59
+- ✅ RSET command to unmark deletions
60
+- ✅ NOOP keepalive command
61
+- ✅ Optional TLS/SSL support (POP3S)
62
+
63
+### POP3 Examples
64
+
65
+The repository includes comprehensive POP3 examples:
66
+
67
+#### Rust Examples
68
+
69
+```bash
70
+# Run the POP3 client example
71
+cargo run --example pop3_client
72
+
73
+# Run the POP3 server example
74
+cargo run --example pop3_server
75
+
76
+# Run integration tests
77
+cargo run --example pop3_integration_test
78
+```
79
+
80
+#### Python Example
81
+
82
+```bash
83
+# Run the Python POP3 client
84
+python3 docs/scripts/pop3-client.py
85
+```
86
+
87
+#### Manual Testing with netcat
88
+
89
+```bash
90
+# Connect to POP3 server
91
+nc localhost 110
92
+
93
+# Then type commands:
94
+USER testuser
95
+PASS testpass
96
+STAT
97
+LIST
98
+RETR 1
99
+QUIT
100
+```
101
+
102
+### POP3 Documentation
103
+
104
+For comprehensive POP3 documentation, see [docs/POP3.md](docs/POP3.md), which includes:
105
+
106
+- Protocol basics and command reference
107
+- Setup and configuration guide
108
+- Usage examples for clients and servers
109
+- Unit and integration testing examples
110
+- Integration with SMTP and IMAP
111
+- Troubleshooting guide
112
+- Best practices for security and performance
113
+
114
+### POP3 Quick Start
115
+
116
+```rust
117
+// Example: Connect to POP3 server and retrieve messages
118
+use std::io::{BufRead, BufReader, Write};
119
+use std::net::TcpStream;
120
+
121
+fn main() -> Result<(), Box<dyn std::error::Error>> {
122
+ let mut stream = TcpStream::connect("localhost:110")?;
123
+ let mut reader = BufReader::new(stream.try_clone()?);
124
+
125
+ // Read greeting
126
+ let mut greeting = String::new();
127
+ reader.read_line(&mut greeting)?;
128
+ println!("{}", greeting);
129
+
130
+ // Authenticate
131
+ writeln!(stream, "USER testuser")?;
132
+ let mut response = String::new();
133
+ reader.read_line(&mut response)?;
134
+
135
+ writeln!(stream, "PASS testpass")?;
136
+ response.clear();
137
+ reader.read_line(&mut response)?;
138
+
139
+ // Get mailbox statistics
140
+ writeln!(stream, "STAT")?;
141
+ response.clear();
142
+ reader.read_line(&mut response)?;
143
+ println!("{}", response);
144
+
145
+ // Close connection
146
+ writeln!(stream, "QUIT")?;
147
+
148
+ Ok(())
149
+}
150
+```
151
+
152
+See [examples/pop3_client.rs](examples/pop3_client.rs) for a complete client implementation with all POP3 commands.
153
+
154
## Dependencies
155
156
- `tokio` - Async runtime
docs/POP3.md
new
+844
@@ -0,0 +1,844 @@
1
+# POP3 Protocol Documentation
2
+
3
+This document provides comprehensive information about the POP3 (Post Office Protocol version 3) implementation, including setup, usage, testing, and integration with other mail protocols.
4
+
5
+## Table of Contents
6
+
7
+1. [Overview](#overview)
8
+2. [POP3 Protocol Basics](#pop3-protocol-basics)
9
+3. [Setup and Configuration](#setup-and-configuration)
10
+4. [Usage Examples](#usage-examples)
11
+5. [Testing](#testing)
12
+6. [Integration with SMTP and IMAP](#integration-with-smtp-and-imap)
13
+7. [Troubleshooting](#troubleshooting)
14
+8. [Best Practices](#best-practices)
15
+
16
+## Overview
17
+
18
+POP3 (Post Office Protocol version 3) is an Internet standard protocol used by email clients to retrieve emails from a mail server. It operates on TCP port 110 (or 995 for POP3S with SSL/TLS).
19
+
20
+### Key Features
21
+
22
+- Simple authentication mechanism (USER/PASS)
23
+- Message retrieval and deletion
24
+- Mailbox statistics and listing
25
+- Minimal server-side state
26
+- Optional TLS/SSL support (POP3S)
27
+
28
+### POP3 vs IMAP
29
+
30
+| Feature | POP3 | IMAP |
31
+|---------|------|------|
32
+| **Purpose** | Download and delete | Access and manage |
33
+| **Messages** | Downloaded to client | Stored on server |
34
+| **Folders** | No folder support | Multiple folders |
35
+| **Offline Access** | Yes (after download) | Limited |
36
+| **Multiple Clients** | Not ideal | Well supported |
37
+| **Server Storage** | Minimal after download | Persistent |
38
+
39
+## POP3 Protocol Basics
40
+
41
+### Connection States
42
+
43
+A POP3 session goes through three states:
44
+
45
+1. **AUTHORIZATION** - Client authenticates with the server
46
+2. **TRANSACTION** - Client retrieves and manages messages
47
+3. **UPDATE** - Server commits changes (deletions) and closes connection
48
+
49
+### Standard Commands
50
+
51
+#### Authorization State Commands
52
+
53
+| Command | Description | Example |
54
+|---------|-------------|---------|
55
+| `USER` | Specify username | `USER john@example.com` |
56
+| `PASS` | Specify password | `PASS secretpassword` |
57
+| `QUIT` | Close connection | `QUIT` |
58
+
59
+#### Transaction State Commands
60
+
61
+| Command | Description | Example |
62
+|---------|-------------|---------|
63
+| `STAT` | Get mailbox statistics | `STAT` |
64
+| `LIST` | List messages | `LIST` or `LIST 1` |
65
+| `RETR` | Retrieve message | `RETR 1` |
66
+| `DELE` | Mark message for deletion | `DELE 1` |
67
+| `NOOP` | No operation (keepalive) | `NOOP` |
68
+| `RSET` | Unmark deleted messages | `RSET` |
69
+| `TOP` | Get message headers | `TOP 1 10` |
70
+| `UIDL` | Get unique message ID | `UIDL` or `UIDL 1` |
71
+| `QUIT` | Close and commit | `QUIT` |
72
+
73
+### Response Format
74
+
75
+POP3 uses two types of responses:
76
+
77
+- **Positive**: `+OK` followed by optional information
78
+- **Negative**: `-ERR` followed by error message
79
+
80
+Multi-line responses end with a line containing only `.` (period).
81
+
82
+### Example Session
83
+
84
+```
85
+S: +OK POP3 server ready
86
+C: USER testuser
87
+S: +OK User accepted
88
+C: PASS testpass
89
+S: +OK Pass accepted
90
+C: STAT
91
+S: +OK 2 320
92
+C: LIST
93
+S: +OK 2 messages (320 octets)
94
+S: 1 120
95
+S: 2 200
96
+S: .
97
+C: RETR 1
98
+S: +OK 120 octets
99
+S: [message content]
100
+S: .
101
+C: DELE 1
102
+S: +OK message 1 deleted
103
+C: QUIT
104
+S: +OK POP3 server signing off
105
+```
106
+
107
+## Setup and Configuration
108
+
109
+### Basic Server Configuration
110
+
111
+```rust
112
+// Example POP3 server configuration structure
113
+pub struct Pop3Config {
114
+ /// Listening address
115
+ pub host: String,
116
+
117
+ /// Listening port (default: 110)
118
+ pub port: u16,
119
+
120
+ /// Enable TLS/SSL (POP3S on port 995)
121
+ pub enable_tls: bool,
122
+
123
+ /// Path to TLS certificate (if TLS enabled)
124
+ pub tls_cert_path: Option<String>,
125
+
126
+ /// Path to TLS private key (if TLS enabled)
127
+ pub tls_key_path: Option<String>,
128
+
129
+ /// Maximum message size (bytes)
130
+ pub max_message_size: usize,
131
+
132
+ /// Session timeout (seconds)
133
+ pub timeout: u64,
134
+
135
+ /// Authentication backend
136
+ pub auth_backend: AuthBackend,
137
+
138
+ /// Message storage backend
139
+ pub storage_backend: StorageBackend,
140
+}
141
+
142
+impl Default for Pop3Config {
143
+ fn default() -> Self {
144
+ Pop3Config {
145
+ host: "127.0.0.1".to_string(),
146
+ port: 110,
147
+ enable_tls: false,
148
+ tls_cert_path: None,
149
+ tls_key_path: None,
150
+ max_message_size: 10 * 1024 * 1024, // 10MB
151
+ timeout: 600, // 10 minutes
152
+ auth_backend: AuthBackend::File,
153
+ storage_backend: StorageBackend::Maildir,
154
+ }
155
+ }
156
+}
157
+```
158
+
159
+### Starting the Server
160
+
161
+```rust
162
+use smbcloud_mail::pop3::Pop3Server;
163
+use smbcloud_mail::pop3::Pop3Config;
164
+
165
+#[tokio::main]
166
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
167
+ // Initialize logging
168
+ tracing_subscriber::fmt::init();
169
+
170
+ // Create configuration
171
+ let config = Pop3Config {
172
+ host: "127.0.0.1".to_string(),
173
+ port: 110,
174
+ enable_tls: false,
175
+ ..Default::default()
176
+ };
177
+
178
+ // Create and start server
179
+ let server = Pop3Server::new(config);
180
+ server.listen().await?;
181
+
182
+ Ok(())
183
+}
184
+```
185
+
186
+### Configuration File Example
187
+
188
+Create a `pop3.toml` configuration file:
189
+
190
+```toml
191
+[server]
192
+host = "127.0.0.1"
193
+port = 110
194
+timeout = 600
195
+
196
+[tls]
197
+enabled = false
198
+cert_path = "/path/to/cert.pem"
199
+key_path = "/path/to/key.pem"
200
+
201
+[limits]
202
+max_message_size = 10485760 # 10MB in bytes
203
+max_connections = 100
204
+
205
+[authentication]
206
+backend = "file"
207
+users_file = "/etc/pop3/users.txt"
208
+
209
+[storage]
210
+backend = "maildir"
211
+maildir_path = "/var/mail"
212
+```
213
+
214
+## Usage Examples
215
+
216
+### Using the Rust Client Example
217
+
218
+```bash
219
+# Run the POP3 client example
220
+cargo run --example pop3_client
221
+```
222
+
223
+### Using Python
224
+
225
+```bash
226
+# Run the Python POP3 client
227
+python3 docs/scripts/pop3-client.py
228
+```
229
+
230
+### Using netcat (nc) for Testing
231
+
232
+```bash
233
+# Connect to POP3 server
234
+nc localhost 110
235
+
236
+# Then type commands:
237
+USER testuser
238
+PASS testpass
239
+STAT
240
+LIST
241
+RETR 1
242
+QUIT
243
+```
244
+
245
+### Using telnet
246
+
247
+```bash
248
+telnet localhost 110
249
+```
250
+
251
+Then issue POP3 commands manually.
252
+
253
+### Using mail clients
254
+
255
+Configure your email client with:
256
+- **Server**: localhost (or your server address)
257
+- **Port**: 110 (or 995 for POP3S)
258
+- **Username**: Your username
259
+- **Password**: Your password
260
+- **Security**: None (or SSL/TLS for POP3S)
261
+
262
+## Testing
263
+
264
+### Unit Tests
265
+
266
+Example unit tests for POP3 functionality:
267
+
268
+```rust
269
+#[cfg(test)]
270
+mod tests {
271
+ use super::*;
272
+
273
+ #[tokio::test]
274
+ async fn test_pop3_connect() {
275
+ let config = Pop3Config::default();
276
+ let server = Pop3Server::new(config);
277
+ // Test server initialization
278
+ assert!(server.is_ok());
279
+ }
280
+
281
+ #[tokio::test]
282
+ async fn test_pop3_authentication() {
283
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
284
+ let result = client.authenticate("testuser", "testpass").await;
285
+ assert!(result.is_ok());
286
+ }
287
+
288
+ #[tokio::test]
289
+ async fn test_pop3_stat() {
290
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
291
+ client.authenticate("testuser", "testpass").await.unwrap();
292
+ let (count, size) = client.stat().await.unwrap();
293
+ assert!(count >= 0);
294
+ assert!(size >= 0);
295
+ }
296
+
297
+ #[tokio::test]
298
+ async fn test_pop3_list() {
299
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
300
+ client.authenticate("testuser", "testpass").await.unwrap();
301
+ let messages = client.list().await.unwrap();
302
+ assert!(messages.is_ok());
303
+ }
304
+
305
+ #[tokio::test]
306
+ async fn test_pop3_retrieve() {
307
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
308
+ client.authenticate("testuser", "testpass").await.unwrap();
309
+
310
+ let (count, _) = client.stat().await.unwrap();
311
+ if count > 0 {
312
+ let message = client.retrieve(1).await.unwrap();
313
+ assert!(!message.is_empty());
314
+ }
315
+ }
316
+
317
+ #[tokio::test]
318
+ async fn test_pop3_delete() {
319
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
320
+ client.authenticate("testuser", "testpass").await.unwrap();
321
+
322
+ let (count, _) = client.stat().await.unwrap();
323
+ if count > 0 {
324
+ let result = client.delete(1).await;
325
+ assert!(result.is_ok());
326
+
327
+ // Reset to unmark
328
+ client.reset().await.unwrap();
329
+ }
330
+ }
331
+
332
+ #[tokio::test]
333
+ async fn test_pop3_invalid_credentials() {
334
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
335
+ let result = client.authenticate("invalid", "invalid").await;
336
+ assert!(result.is_err());
337
+ }
338
+}
339
+```
340
+
341
+### Integration Tests
342
+
343
+```rust
344
+#[cfg(test)]
345
+mod integration_tests {
346
+ use super::*;
347
+ use tokio::time::{sleep, Duration};
348
+
349
+ #[tokio::test]
350
+ async fn test_complete_pop3_session() {
351
+ // Start POP3 server
352
+ let config = Pop3Config::default();
353
+ let server = Pop3Server::new(config);
354
+
355
+ tokio::spawn(async move {
356
+ server.listen().await.unwrap();
357
+ });
358
+
359
+ // Wait for server to start
360
+ sleep(Duration::from_millis(100)).await;
361
+
362
+ // Connect client
363
+ let mut client = Pop3Client::connect("localhost", 110).await.unwrap();
364
+
365
+ // Authenticate
366
+ client.authenticate("testuser", "testpass").await.unwrap();
367
+
368
+ // Get statistics
369
+ let (count, size) = client.stat().await.unwrap();
370
+ assert!(count >= 0);
371
+
372
+ // List messages
373
+ let messages = client.list().await.unwrap();
374
+ assert_eq!(messages.len(), count);
375
+
376
+ // Close
377
+ client.quit().await.unwrap();
378
+ }
379
+
380
+ #[tokio::test]
381
+ async fn test_smtp_to_pop3_integration() {
382
+ // Send email via SMTP
383
+ let smtp_client = SmtpClient::connect("localhost", 2525).await.unwrap();
384
+ smtp_client.send_email(
385
+ "sender@example.com",
386
+ "recipient@example.com",
387
+ "Test Subject",
388
+ "Test Body"
389
+ ).await.unwrap();
390
+
391
+ // Wait for delivery
392
+ sleep(Duration::from_millis(100)).await;
393
+
394
+ // Retrieve via POP3
395
+ let mut pop3_client = Pop3Client::connect("localhost", 110).await.unwrap();
396
+ pop3_client.authenticate("recipient@example.com", "password").await.unwrap();
397
+
398
+ let (count, _) = pop3_client.stat().await.unwrap();
399
+ assert!(count > 0);
400
+
401
+ let message = pop3_client.retrieve(count).await.unwrap();
402
+ assert!(message.contains("Test Subject"));
403
+ assert!(message.contains("Test Body"));
404
+ }
405
+}
406
+```
407
+
408
+### Mock Testing
409
+
410
+```rust
411
+use mockall::predicate::*;
412
+use mockall::*;
413
+
414
+#[automock]
415
+pub trait Pop3Storage {
416
+ fn get_message(&self, user: &str, msg_id: usize) -> Result<String, Error>;
417
+ fn list_messages(&self, user: &str) -> Result<Vec<(usize, usize)>, Error>;
418
+ fn delete_message(&self, user: &str, msg_id: usize) -> Result<(), Error>;
419
+}
420
+
421
+#[cfg(test)]
422
+mod mock_tests {
423
+ use super::*;
424
+
425
+ #[test]
426
+ fn test_with_mock_storage() {
427
+ let mut mock = MockPop3Storage::new();
428
+
429
+ mock.expect_get_message()
430
+ .with(eq("testuser"), eq(1))
431
+ .times(1)
432
+ .returning(|_, _| Ok("From: test@example.com\n\nTest message".to_string()));
433
+
434
+ mock.expect_list_messages()
435
+ .with(eq("testuser"))
436
+ .times(1)
437
+ .returning(|_| Ok(vec![(1, 100), (2, 200)]));
438
+
439
+ // Test with mock
440
+ let message = mock.get_message("testuser", 1).unwrap();
441
+ assert!(message.contains("Test message"));
442
+
443
+ let messages = mock.list_messages("testuser").unwrap();
444
+ assert_eq!(messages.len(), 2);
445
+ }
446
+}
447
+```
448
+
449
+## Integration with SMTP and IMAP
450
+
451
+### Shared Architecture
452
+
453
+```
454
+┌─────────────────────────────────────────────┐
455
+│ Mail Server Architecture │
456
+├─────────────────────────────────────────────┤
457
+│ │
458
+│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
459
+│ │ SMTP │ │ POP3 │ │ IMAP │ │
460
+│ │ Server │ │ Server │ │ Server │ │
461
+│ │ Port 25 │ │ Port 110 │ │ Port 143 │ │
462
+│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
463
+│ │ │ │ │
464
+│ └─────────────┴─────────────┘ │
465
+│ │ │
466
+│ ┌───────────▼───────────┐ │
467
+│ │ Message Storage │ │
468
+│ │ (Maildir/mbox/DB) │ │
469
+│ └───────────────────────┘ │
470
+│ │ │
471
+│ ┌───────────▼───────────┐ │
472
+│ │ User Authentication │ │
473
+│ │ (LDAP/DB/File) │ │
474
+│ └───────────────────────┘ │
475
+└─────────────────────────────────────────────┘
476
+```
477
+
478
+### Shared Storage Example
479
+
480
+```rust
481
+pub trait MessageStorage: Send + Sync {
482
+ /// Store a new message
483
+ fn store_message(&self, user: &str, message: &[u8]) -> Result<String, Error>;
484
+
485
+ /// Get a message by ID
486
+ fn get_message(&self, user: &str, message_id: &str) -> Result<Vec<u8>, Error>;
487
+
488
+ /// List all messages for a user
489
+ fn list_messages(&self, user: &str) -> Result<Vec<MessageInfo>, Error>;
490
+
491
+ /// Delete a message
492
+ fn delete_message(&self, user: &str, message_id: &str) -> Result<(), Error>;
493
+
494
+ /// Mark message as seen (for IMAP)
495
+ fn mark_seen(&self, user: &str, message_id: &str) -> Result<(), Error>;
496
+}
497
+
498
+// Maildir implementation (works for all protocols)
499
+pub struct MaildirStorage {
500
+ base_path: PathBuf,
501
+}
502
+
503
+impl MessageStorage for MaildirStorage {
504
+ fn store_message(&self, user: &str, message: &[u8]) -> Result<String, Error> {
505
+ let user_dir = self.base_path.join(user).join("new");
506
+ std::fs::create_dir_all(&user_dir)?;
507
+
508
+ let msg_id = format!("{}.{}", SystemTime::now()
509
+ .duration_since(UNIX_EPOCH)?.as_secs(), uuid::Uuid::new_v4());
510
+
511
+ let msg_path = user_dir.join(&msg_id);
512
+ std::fs::write(msg_path, message)?;
513
+
514
+ Ok(msg_id)
515
+ }
516
+
517
+ fn get_message(&self, user: &str, message_id: &str) -> Result<Vec<u8>, Error> {
518
+ let paths = [
519
+ self.base_path.join(user).join("new").join(message_id),
520
+ self.base_path.join(user).join("cur").join(message_id),
521
+ ];
522
+
523
+ for path in &paths {
524
+ if path.exists() {
525
+ return Ok(std::fs::read(path)?);
526
+ }
527
+ }
528
+
529
+ Err(Error::MessageNotFound)
530
+ }
531
+
532
+ // ... other methods
533
+}
534
+```
535
+
536
+### User Management Example
537
+
538
+```rust
539
+pub trait UserAuthenticator: Send + Sync {
540
+ fn authenticate(&self, username: &str, password: &str) -> Result<bool, Error>;
541
+ fn user_exists(&self, username: &str) -> Result<bool, Error>;
542
+}
543
+
544
+// File-based authentication (simple example)
545
+pub struct FileAuthenticator {
546
+ users_file: PathBuf,
547
+}
548
+
549
+impl UserAuthenticator for FileAuthenticator {
550
+ fn authenticate(&self, username: &str, password: &str) -> Result<bool, Error> {
551
+ let contents = std::fs::read_to_string(&self.users_file)?;
552
+
553
+ for line in contents.lines() {
554
+ let parts: Vec<&str> = line.split(':').collect();
555
+ if parts.len() >= 2 && parts[0] == username {
556
+ // In production, use proper password hashing (bcrypt, argon2, etc.)
557
+ return Ok(parts[1] == password);
558
+ }
559
+ }
560
+
561
+ Ok(false)
562
+ }
563
+
564
+ fn user_exists(&self, username: &str) -> Result<bool, Error> {
565
+ let contents = std::fs::read_to_string(&self.users_file)?;
566
+ Ok(contents.lines().any(|line| line.starts_with(username)))
567
+ }
568
+}
569
+```
570
+
571
+### Complete Integration Example
572
+
573
+```rust
574
+use std::sync::Arc;
575
+
576
+pub struct MailServer {
577
+ smtp_server: SmtpServer,
578
+ pop3_server: Pop3Server,
579
+ imap_server: ImapServer,
580
+ storage: Arc<dyn MessageStorage>,
581
+ auth: Arc<dyn UserAuthenticator>,
582
+}
583
+
584
+impl MailServer {
585
+ pub fn new(config: MailServerConfig) -> Result<Self, Error> {
586
+ let storage = Arc::new(MaildirStorage::new(&config.maildir_path)?);
587
+ let auth = Arc::new(FileAuthenticator::new(&config.users_file)?);
588
+
589
+ Ok(MailServer {
590
+ smtp_server: SmtpServer::new(
591
+ config.smtp_config,
592
+ storage.clone(),
593
+ auth.clone()
594
+ ),
595
+ pop3_server: Pop3Server::new(
596
+ config.pop3_config,
597
+ storage.clone(),
598
+ auth.clone()
599
+ ),
600
+ imap_server: ImapServer::new(
601
+ config.imap_config,
602
+ storage.clone(),
603
+ auth.clone()
604
+ ),
605
+ storage,
606
+ auth,
607
+ })
608
+ }
609
+
610
+ pub async fn start(&self) -> Result<(), Error> {
611
+ // Start all servers concurrently
612
+ tokio::try_join!(
613
+ self.smtp_server.listen(),
614
+ self.pop3_server.listen(),
615
+ self.imap_server.listen(),
616
+ )?;
617
+
618
+ Ok(())
619
+ }
620
+}
621
+
622
+// Usage
623
+#[tokio::main]
624
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
625
+ let config = MailServerConfig::from_file("mail-server.toml")?;
626
+ let server = MailServer::new(config)?;
627
+ server.start().await?;
628
+ Ok(())
629
+}
630
+```
631
+
632
+## Troubleshooting
633
+
634
+### Common Issues
635
+
636
+#### 1. Connection Refused
637
+
638
+**Problem**: Client cannot connect to server
639
+
640
+**Possible Causes**:
641
+- Server not running
642
+- Firewall blocking port 110
643
+- Wrong host/port configuration
644
+
645
+**Solutions**:
646
+```bash
647
+# Check if server is running
648
+netstat -an | grep :110
649
+
650
+# Test connection
651
+telnet localhost 110
652
+
653
+# Check firewall
654
+sudo ufw status
655
+sudo ufw allow 110/tcp
656
+```
657
+
658
+#### 2. Authentication Failures
659
+
660
+**Problem**: `USER` or `PASS` commands return `-ERR`
661
+
662
+**Possible Causes**:
663
+- Wrong username/password
664
+- User doesn't exist
665
+- Authentication backend not configured
666
+
667
+**Solutions**:
668
+- Verify credentials
669
+- Check server logs
670
+- Test with known-good credentials
671
+
672
+```bash
673
+# Check server logs
674
+tail -f /var/log/pop3/server.log
675
+
676
+# Test authentication
677
+echo -e "USER testuser\r\nPASS testpass\r\nQUIT\r\n" | nc localhost 110
678
+```
679
+
680
+#### 3. Messages Not Found
681
+
682
+**Problem**: `RETR` or `DELE` commands fail
683
+
684
+**Possible Causes**:
685
+- Invalid message number
686
+- Message already deleted
687
+- Storage backend issues
688
+
689
+**Solutions**:
690
+```bash
691
+# List messages first
692
+echo -e "USER testuser\r\nPASS testpass\r\nLIST\r\nQUIT\r\n" | nc localhost 110
693
+
694
+# Check mailbox directory
695
+ls -la /var/mail/testuser/new/
696
+ls -la /var/mail/testuser/cur/
697
+```
698
+
699
+#### 4. Connection Timeouts
700
+
701
+**Problem**: Connection drops after period of inactivity
702
+
703
+**Possible Causes**:
704
+- Server timeout too short
705
+- Network issues
706
+- Firewall timeout
707
+
708
+**Solutions**:
709
+- Increase server timeout setting
710
+- Send periodic `NOOP` commands
711
+- Adjust firewall settings
712
+
713
+#### 5. TLS/SSL Errors
714
+
715
+**Problem**: POP3S connection fails
716
+
717
+**Possible Causes**:
718
+- Invalid certificate
719
+- Wrong port (should be 995)
720
+- TLS not enabled on server
721
+
722
+**Solutions**:
723
+```bash
724
+# Test TLS connection
725
+openssl s_client -connect localhost:995
726
+
727
+# Verify certificate
728
+openssl x509 -in /path/to/cert.pem -text -noout
729
+```
730
+
731
+### Debug Mode
732
+
733
+Enable debug logging:
734
+
735
+```rust
736
+use tracing_subscriber;
737
+
738
+fn main() {
739
+ tracing_subscriber::fmt()
740
+ .with_max_level(tracing::Level::DEBUG)
741
+ .init();
742
+
743
+ // ... start server
744
+}
745
+```
746
+
747
+### Network Debugging
748
+
749
+```bash
750
+# Capture POP3 traffic
751
+sudo tcpdump -i any -A port 110
752
+
753
+# Monitor connections
754
+watch -n 1 'netstat -an | grep :110'
755
+
756
+# Check server process
757
+ps aux | grep pop3
758
+```
759
+
760
+## Best Practices
761
+
762
+### Security
763
+
764
+1. **Use TLS/SSL**: Always use POP3S (port 995) for production
765
+2. **Strong Authentication**: Use secure password hashing (bcrypt, argon2)
766
+3. **Rate Limiting**: Implement login attempt rate limiting
767
+4. **Input Validation**: Validate all client inputs
768
+5. **Timeouts**: Set appropriate connection timeouts
769
+
770
+### Performance
771
+
772
+1. **Connection Pooling**: Reuse database connections
773
+2. **Caching**: Cache frequently accessed data
774
+3. **Async I/O**: Use async/await for I/O operations
775
+4. **Resource Limits**: Set max connections, message sizes
776
+5. **Indexing**: Index message storage for fast lookups
777
+
778
+### Reliability
779
+
780
+1. **Error Handling**: Handle all errors gracefully
781
+2. **Logging**: Log all operations for debugging
782
+3. **Monitoring**: Monitor server health and metrics
783
+4. **Backups**: Regular backup of message storage
784
+5. **Testing**: Comprehensive unit and integration tests
785
+
786
+### Client Development
787
+
788
+1. **Connection Management**: Properly close connections
789
+2. **Error Recovery**: Handle and retry failed operations
790
+3. **Timeouts**: Set reasonable timeouts
791
+4. **Message Handling**: Process messages efficiently
792
+5. **State Tracking**: Track message states (seen, deleted)
793
+
794
+### Example: Secure Production Configuration
795
+
796
+```toml
797
+[server]
798
+host = "0.0.0.0"
799
+port = 110
800
+timeout = 600
801
+
802
+[tls]
803
+enabled = true
804
+port = 995
805
+cert_path = "/etc/ssl/certs/pop3.crt"
806
+key_path = "/etc/ssl/private/pop3.key"
807
+min_tls_version = "1.2"
808
+
809
+[security]
810
+max_auth_attempts = 3
811
+auth_timeout = 30
812
+rate_limit_per_ip = 10
813
+
814
+[limits]
815
+max_message_size = 52428800 # 50MB
816
+max_connections = 1000
817
+max_messages_per_session = 1000
818
+
819
+[logging]
820
+level = "info"
821
+file = "/var/log/pop3/server.log"
822
+max_size = "100MB"
823
+max_age = 30 # days
824
+
825
+[authentication]
826
+backend = "database"
827
+db_url = "postgresql://user:pass@localhost/maildb"
828
+
829
+[storage]
830
+backend = "maildir"
831
+maildir_path = "/var/mail"
832
+```
833
+
834
+## Additional Resources
835
+
836
+- [RFC 1939 - POP3 Protocol](https://tools.ietf.org/html/rfc1939)
837
+- [RFC 2595 - POP3 with TLS](https://tools.ietf.org/html/rfc2595)
838
+- [RFC 2449 - POP3 Extension Mechanism](https://tools.ietf.org/html/rfc2449)
839
+
840
+## Support
841
+
842
+For issues, questions, or contributions:
843
+- GitHub Issues: [Repository Issues](https://github.com/smbcloudXYZ/smbcloud-mail-smtp/issues)
844
+- Documentation: [Project Wiki](https://github.com/smbcloudXYZ/smbcloud-mail-smtp/wiki)
docs/config-examples.md
new
+676
@@ -0,0 +1,676 @@
1
+# Mail Server Configuration Examples
2
+
3
+This document provides example configurations for the mail server, including SMTP, POP3, and IMAP settings.
4
+
5
+## Table of Contents
6
+
7
+1. [Basic Configuration](#basic-configuration)
8
+2. [POP3 Server Configuration](#pop3-server-configuration)
9
+3. [SMTP Server Configuration](#smtp-server-configuration)
10
+4. [Integrated Mail Server Configuration](#integrated-mail-server-configuration)
11
+5. [Security Configuration](#security-configuration)
12
+6. [Production Configuration](#production-configuration)
13
+
14
+## Basic Configuration
15
+
16
+### Simple POP3 Server (Development)
17
+
18
+```toml
19
+# pop3-dev.toml
20
+[server]
21
+host = "127.0.0.1"
22
+port = 110
23
+timeout = 600 # 10 minutes
24
+
25
+[authentication]
26
+# Simple file-based authentication for development
27
+backend = "file"
28
+users_file = "users.txt"
29
+
30
+[storage]
31
+# Maildir storage backend
32
+backend = "maildir"
33
+maildir_path = "./mailboxes"
34
+```
35
+
36
+**users.txt** format:
37
+```
38
+testuser:testpass
39
+alice:password123
40
+bob:secret
41
+```
42
+
43
+### Simple SMTP Server (Development)
44
+
45
+```toml
46
+# smtp-dev.toml
47
+[server]
48
+host = "127.0.0.1"
49
+port = 2525
50
+timeout = 300
51
+
52
+[storage]
53
+backend = "maildir"
54
+maildir_path = "./mailboxes"
55
+
56
+[tls]
57
+enabled = false
58
+```
59
+
60
+## POP3 Server Configuration
61
+
62
+### Minimal POP3 Configuration
63
+
64
+```toml
65
+[server]
66
+host = "0.0.0.0"
67
+port = 110
68
+
69
+[authentication]
70
+backend = "file"
71
+users_file = "/etc/pop3/users.txt"
72
+
73
+[storage]
74
+backend = "maildir"
75
+maildir_path = "/var/mail"
76
+```
77
+
78
+### POP3 with TLS/SSL (POP3S)
79
+
80
+```toml
81
+[server]
82
+host = "0.0.0.0"
83
+port = 995 # POP3S standard port
84
+timeout = 600
85
+
86
+[tls]
87
+enabled = true
88
+cert_path = "/etc/ssl/certs/mail.crt"
89
+key_path = "/etc/ssl/private/mail.key"
90
+min_tls_version = "1.2"
91
+
92
+[authentication]
93
+backend = "database"
94
+db_url = "postgresql://popuser:password@localhost/maildb"
95
+
96
+[storage]
97
+backend = "maildir"
98
+maildir_path = "/var/mail"
99
+
100
+[limits]
101
+max_message_size = 52428800 # 50MB
102
+max_connections = 500
103
+max_messages_per_session = 1000
104
+
105
+[logging]
106
+level = "info"
107
+file = "/var/log/pop3/server.log"
108
+```
109
+
110
+### POP3 with Database Authentication
111
+
112
+```toml
113
+[server]
114
+host = "0.0.0.0"
115
+port = 110
116
+
117
+[authentication]
118
+backend = "database"
119
+db_type = "postgresql"
120
+db_url = "postgresql://user:password@localhost:5432/maildb"
121
+# SQL query to authenticate user
122
+auth_query = "SELECT password_hash FROM users WHERE email = $1 AND active = true"
123
+# Password hashing algorithm
124
+password_hash = "bcrypt"
125
+
126
+[storage]
127
+backend = "database"
128
+db_url = "postgresql://user:password@localhost:5432/maildb"
129
+```
130
+
131
+### POP3 with LDAP Authentication
132
+
133
+```toml
134
+[server]
135
+host = "0.0.0.0"
136
+port = 110
137
+
138
+[authentication]
139
+backend = "ldap"
140
+ldap_url = "ldap://ldap.example.com:389"
141
+ldap_base_dn = "ou=users,dc=example,dc=com"
142
+ldap_bind_dn = "cn=admin,dc=example,dc=com"
143
+ldap_bind_password = "admin_password"
144
+ldap_user_filter = "(&(objectClass=person)(mail=%u))"
145
+
146
+[storage]
147
+backend = "maildir"
148
+maildir_path = "/var/mail"
149
+```
150
+
151
+## SMTP Server Configuration
152
+
153
+### Basic SMTP Configuration
154
+
155
+```toml
156
+[server]
157
+host = "0.0.0.0"
158
+port = 25
159
+timeout = 300
160
+
161
+[relay]
162
+# Allow relay from local network
163
+allowed_networks = ["127.0.0.0/8", "192.168.0.0/16"]
164
+
165
+[storage]
166
+backend = "maildir"
167
+maildir_path = "/var/mail"
168
+
169
+[limits]
170
+max_message_size = 10485760 # 10MB
171
+max_recipients = 100
172
+```
173
+
174
+### SMTP with Authentication and TLS
175
+
176
+```toml
177
+[server]
178
+host = "0.0.0.0"
179
+port = 587 # Submission port
180
+timeout = 300
181
+
182
+[tls]
183
+enabled = true
184
+starttls = true # Allow STARTTLS
185
+cert_path = "/etc/ssl/certs/mail.crt"
186
+key_path = "/etc/ssl/private/mail.key"
187
+
188
+[authentication]
189
+required = true # Require authentication for sending
190
+backend = "database"
191
+db_url = "postgresql://user:password@localhost/maildb"
192
+
193
+[storage]
194
+backend = "maildir"
195
+maildir_path = "/var/mail"
196
+```
197
+
198
+## Integrated Mail Server Configuration
199
+
200
+### Complete Mail Server (SMTP + POP3 + IMAP)
201
+
202
+```toml
203
+# mail-server.toml
204
+
205
+[global]
206
+hostname = "mail.example.com"
207
+domain = "example.com"
208
+
209
+# Shared authentication backend
210
+[authentication]
211
+backend = "database"
212
+db_url = "postgresql://mailuser:password@localhost:5432/maildb"
213
+password_hash = "argon2"
214
+
215
+# Shared storage backend
216
+[storage]
217
+backend = "maildir"
218
+maildir_path = "/var/mail"
219
+# Alternative: use database storage
220
+# backend = "database"
221
+# db_url = "postgresql://mailuser:password@localhost:5432/maildb"
222
+
223
+# SMTP Server Configuration
224
+[smtp]
225
+enabled = true
226
+host = "0.0.0.0"
227
+port = 25
228
+submission_port = 587
229
+timeout = 300
230
+
231
+[smtp.tls]
232
+enabled = true
233
+starttls = true
234
+cert_path = "/etc/ssl/certs/mail.crt"
235
+key_path = "/etc/ssl/private/mail.key"
236
+
237
+[smtp.limits]
238
+max_message_size = 52428800 # 50MB
239
+max_recipients = 100
240
+max_connections = 1000
241
+
242
+# POP3 Server Configuration
243
+[pop3]
244
+enabled = true
245
+host = "0.0.0.0"
246
+port = 110
247
+timeout = 600
248
+
249
+[pop3.tls]
250
+enabled = true
251
+port = 995 # POP3S port
252
+cert_path = "/etc/ssl/certs/mail.crt"
253
+key_path = "/etc/ssl/private/mail.key"
254
+
255
+[pop3.limits]
256
+max_message_size = 52428800
257
+max_connections = 500
258
+max_messages_per_session = 1000
259
+
260
+# IMAP Server Configuration
261
+[imap]
262
+enabled = true
263
+host = "0.0.0.0"
264
+port = 143
265
+timeout = 1800 # 30 minutes
266
+
267
+[imap.tls]
268
+enabled = true
269
+starttls = true
270
+port = 993 # IMAPS port
271
+cert_path = "/etc/ssl/certs/mail.crt"
272
+key_path = "/etc/ssl/private/mail.key"
273
+
274
+[imap.limits]
275
+max_message_size = 52428800
276
+max_connections = 1000
277
+
278
+# Logging Configuration
279
+[logging]
280
+level = "info"
281
+format = "json"
282
+smtp_log = "/var/log/mail/smtp.log"
283
+pop3_log = "/var/log/mail/pop3.log"
284
+imap_log = "/var/log/mail/imap.log"
285
+max_size = "100MB"
286
+max_age = 30 # days
287
+compress = true
288
+```
289
+
290
+## Security Configuration
291
+
292
+### Security Best Practices Configuration
293
+
294
+```toml
295
+[security]
296
+# Rate limiting
297
+[security.rate_limit]
298
+enabled = true
299
+max_connections_per_ip = 10
300
+max_auth_attempts = 3
301
+auth_failure_delay = 5 # seconds
302
+ban_duration = 3600 # 1 hour
303
+
304
+# IP whitelist/blacklist
305
+[security.access_control]
306
+whitelist = ["192.168.1.0/24", "10.0.0.0/8"]
307
+blacklist = ["203.0.113.0/24"]
308
+
309
+# Password policies
310
+[security.passwords]
311
+min_length = 12
312
+require_uppercase = true
313
+require_lowercase = true
314
+require_digit = true
315
+require_special = true
316
+hash_algorithm = "argon2"
317
+hash_iterations = 3
318
+
319
+# TLS settings
320
+[security.tls]
321
+min_version = "1.2"
322
+cipher_suites = [
323
+ "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
324
+ "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
325
+]
326
+prefer_server_ciphers = true
327
+
328
+# Session security
329
+[security.sessions]
330
+timeout = 600
331
+max_idle_time = 300
332
+require_encryption = true
333
+```
334
+
335
+## Production Configuration
336
+
337
+### High-Performance Production Setup
338
+
339
+```toml
340
+# production.toml
341
+
342
+[global]
343
+hostname = "mail.example.com"
344
+domain = "example.com"
345
+environment = "production"
346
+
347
+# Performance settings
348
+[performance]
349
+worker_threads = 16
350
+max_blocking_threads = 512
351
+async_io_threads = 8
352
+
353
+# Connection pooling
354
+[database]
355
+url = "postgresql://mailuser:password@db-cluster.example.com:5432/maildb"
356
+max_connections = 100
357
+min_connections = 10
358
+connection_timeout = 5
359
+idle_timeout = 600
360
+
361
+# Redis cache for sessions and rate limiting
362
+[cache]
363
+backend = "redis"
364
+url = "redis://cache-cluster.example.com:6379"
365
+ttl = 3600
366
+max_connections = 50
367
+
368
+# SMTP Configuration
369
+[smtp]
370
+enabled = true
371
+host = "0.0.0.0"
372
+port = 25
373
+submission_port = 587
374
+timeout = 300
375
+
376
+[smtp.tls]
377
+enabled = true
378
+starttls = true
379
+cert_path = "/etc/ssl/certs/mail.crt"
380
+key_path = "/etc/ssl/private/mail.key"
381
+min_version = "1.2"
382
+
383
+[smtp.limits]
384
+max_message_size = 52428800
385
+max_recipients = 100
386
+max_connections = 2000
387
+rate_limit_per_sender = 100 # messages per hour
388
+
389
+[smtp.queue]
390
+backend = "database"
391
+retry_attempts = 5
392
+retry_delay = 300 # 5 minutes
393
+max_age = 432000 # 5 days
394
+
395
+# POP3 Configuration
396
+[pop3]
397
+enabled = true
398
+host = "0.0.0.0"
399
+port = 110
400
+timeout = 600
401
+
402
+[pop3.tls]
403
+enabled = true
404
+port = 995
405
+cert_path = "/etc/ssl/certs/mail.crt"
406
+key_path = "/etc/ssl/private/mail.key"
407
+
408
+[pop3.limits]
409
+max_connections = 1000
410
+max_messages_per_session = 1000
411
+rate_limit_per_ip = 50 # connections per hour
412
+
413
+# Authentication
414
+[authentication]
415
+backend = "database"
416
+db_url = "postgresql://mailuser:password@db-cluster.example.com:5432/maildb"
417
+password_hash = "argon2"
418
+cache_ttl = 3600
419
+mfa_enabled = true
420
+
421
+# Storage
422
+[storage]
423
+backend = "s3"
424
+s3_bucket = "mail-storage"
425
+s3_region = "us-east-1"
426
+s3_endpoint = "https://s3.amazonaws.com"
427
+# Or use Maildir
428
+# backend = "maildir"
429
+# maildir_path = "/mnt/mail-storage"
430
+
431
+# Monitoring and Metrics
432
+[monitoring]
433
+enabled = true
434
+prometheus_port = 9090
435
+health_check_port = 8080
436
+
437
+[monitoring.metrics]
438
+collect_interval = 60 # seconds
439
+retention_days = 30
440
+
441
+# Logging
442
+[logging]
443
+level = "info"
444
+format = "json"
445
+output = "syslog"
446
+syslog_address = "localhost:514"
447
+syslog_facility = "mail"
448
+
449
+# Backup
450
+[backup]
451
+enabled = true
452
+schedule = "0 2 * * *" # Daily at 2 AM
453
+destination = "s3://backups/mail"
454
+retention_days = 90
455
+```
456
+
457
+### Multi-Node Cluster Configuration
458
+
459
+```toml
460
+# cluster-node.toml
461
+
462
+[cluster]
463
+enabled = true
464
+node_id = "node-1"
465
+nodes = [
466
+ "node-1.example.com:7000",
467
+ "node-2.example.com:7000",
468
+ "node-3.example.com:7000",
469
+]
470
+
471
+[cluster.consensus]
472
+algorithm = "raft"
473
+election_timeout = 1000
474
+heartbeat_interval = 100
475
+
476
+[cluster.replication]
477
+factor = 3
478
+sync_mode = "async"
479
+
480
+# Shared distributed storage
481
+[storage]
482
+backend = "distributed"
483
+cluster_nodes = [
484
+ "storage-1.example.com:9000",
485
+ "storage-2.example.com:9000",
486
+ "storage-3.example.com:9000",
487
+]
488
+
489
+# Load balancer configuration
490
+[load_balancer]
491
+algorithm = "round_robin"
492
+health_check_interval = 5
493
+health_check_timeout = 2
494
+```
495
+
496
+## Environment-Specific Configurations
497
+
498
+### Development
499
+
500
+```toml
501
+[global]
502
+environment = "development"
503
+debug = true
504
+
505
+[server]
506
+host = "127.0.0.1"
507
+
508
+[logging]
509
+level = "debug"
510
+output = "stdout"
511
+
512
+[tls]
513
+enabled = false # Use plain text for easier debugging
514
+```
515
+
516
+### Staging
517
+
518
+```toml
519
+[global]
520
+environment = "staging"
521
+debug = false
522
+
523
+[server]
524
+host = "0.0.0.0"
525
+
526
+[logging]
527
+level = "info"
528
+output = "file"
529
+
530
+[tls]
531
+enabled = true
532
+# Use staging certificates
533
+cert_path = "/etc/ssl/staging/cert.pem"
534
+key_path = "/etc/ssl/staging/key.pem"
535
+```
536
+
537
+### Production
538
+
539
+```toml
540
+[global]
541
+environment = "production"
542
+debug = false
543
+
544
+[server]
545
+host = "0.0.0.0"
546
+
547
+[logging]
548
+level = "warn"
549
+output = "syslog"
550
+
551
+[tls]
552
+enabled = true
553
+cert_path = "/etc/ssl/certs/production.crt"
554
+key_path = "/etc/ssl/private/production.key"
555
+
556
+[security]
557
+strict_mode = true
558
+require_tls = true
559
+```
560
+
561
+## Docker Configuration
562
+
563
+### Docker Compose Example
564
+
565
+```yaml
566
+# docker-compose.yml
567
+version: '3.8'
568
+
569
+services:
570
+ mail-server:
571
+ image: smbcloud-mail:latest
572
+ container_name: mail-server
573
+ ports:
574
+ - "25:25" # SMTP
575
+ - "587:587" # SMTP Submission
576
+ - "110:110" # POP3
577
+ - "995:995" # POP3S
578
+ - "143:143" # IMAP
579
+ - "993:993" # IMAPS
580
+ volumes:
581
+ - ./config:/etc/mail:ro
582
+ - ./mail-data:/var/mail
583
+ - ./logs:/var/log/mail
584
+ - ./certs:/etc/ssl/mail:ro
585
+ environment:
586
+ - RUST_LOG=info
587
+ - CONFIG_FILE=/etc/mail/production.toml
588
+ depends_on:
589
+ - postgres
590
+ - redis
591
+ restart: unless-stopped
592
+
593
+ postgres:
594
+ image: postgres:15
595
+ container_name: mail-db
596
+ volumes:
597
+ - postgres-data:/var/lib/postgresql/data
598
+ environment:
599
+ - POSTGRES_DB=maildb
600
+ - POSTGRES_USER=mailuser
601
+ - POSTGRES_PASSWORD=securepassword
602
+ restart: unless-stopped
603
+
604
+ redis:
605
+ image: redis:7
606
+ container_name: mail-cache
607
+ volumes:
608
+ - redis-data:/data
609
+ restart: unless-stopped
610
+
611
+volumes:
612
+ postgres-data:
613
+ redis-data:
614
+```
615
+
616
+## Configuration Loading in Code
617
+
618
+### Rust Configuration Loading Example
619
+
620
+```rust
621
+use serde::Deserialize;
622
+use std::fs;
623
+
624
+#[derive(Debug, Deserialize)]
625
+struct MailConfig {
626
+ global: GlobalConfig,
627
+ smtp: Option<SmtpConfig>,
628
+ pop3: Option<Pop3Config>,
629
+ imap: Option<ImapConfig>,
630
+ authentication: AuthConfig,
631
+ storage: StorageConfig,
632
+}
633
+
634
+#[derive(Debug, Deserialize)]
635
+struct Pop3Config {
636
+ enabled: bool,
637
+ host: String,
638
+ port: u16,
639
+ timeout: u64,
640
+ tls: Option<TlsConfig>,
641
+ limits: Option<LimitsConfig>,
642
+}
643
+
644
+fn load_config(path: &str) -> Result<MailConfig, Box<dyn std::error::Error>> {
645
+ let contents = fs::read_to_string(path)?;
646
+ let config: MailConfig = toml::from_str(&contents)?;
647
+ Ok(config)
648
+}
649
+
650
+fn main() -> Result<(), Box<dyn std::error::Error>> {
651
+ let config = load_config("mail-server.toml")?;
652
+
653
+ if let Some(pop3_config) = config.pop3 {
654
+ if pop3_config.enabled {
655
+ println!("Starting POP3 server on {}:{}",
656
+ pop3_config.host, pop3_config.port);
657
+ // Start POP3 server with config
658
+ }
659
+ }
660
+
661
+ Ok(())
662
+}
663
+```
664
+
665
+## Notes
666
+
667
+1. Always use strong passwords and secure password hashing (argon2, bcrypt)
668
+2. Enable TLS/SSL for production environments
669
+3. Implement rate limiting to prevent abuse
670
+4. Use database or LDAP authentication in production
671
+5. Regular backup of configuration and data
672
+6. Monitor logs for security issues
673
+7. Keep certificates up to date
674
+8. Use environment variables for sensitive data (passwords, keys)
675
+9. Test configurations in staging before production
676
+10. Document any custom configuration changes
docs/scripts/pop3-client.py
new
+413
@@ -0,0 +1,413 @@
1
+#!/usr/bin/env python3
2
+"""
3
+POP3 Client Example in Python
4
+
5
+This script demonstrates how to use the Python poplib library to interact
6
+with a POP3 server. It shows all major POP3 operations including:
7
+- Connecting to a POP3 server
8
+- Authentication
9
+- Listing messages
10
+- Retrieving messages
11
+- Deleting messages
12
+- Error handling
13
+
14
+Usage:
15
+ python3 pop3-client.py
16
+
17
+Requirements:
18
+ Python 3.6+
19
+"""
20
+
21
+import poplib
22
+from email.parser import Parser
23
+from email.policy import default
24
+import sys
25
+
26
+
27
+def example_basic_connection():
28
+ """Example 1: Basic connection and authentication"""
29
+ print("\n" + "="*50)
30
+ print("EXAMPLE 1: Basic Connection and Authentication")
31
+ print("="*50 + "\n")
32
+
33
+ try:
34
+ # Connect to POP3 server
35
+ print("Connecting to localhost:110...")
36
+ pop3_server = poplib.POP3("localhost", 110)
37
+
38
+ # Read the server greeting
39
+ greeting = pop3_server.getwelcome()
40
+ print(f"Server greeting: {greeting.decode('utf-8')}")
41
+
42
+ # Authenticate
43
+ print("\nAuthenticating...")
44
+ pop3_server.user("testuser")
45
+ pop3_server.pass_("testpass")
46
+ print("Authentication successful!")
47
+
48
+ # Close connection
49
+ pop3_server.quit()
50
+ print("Connection closed")
51
+
52
+ return True
53
+
54
+ except poplib.error_proto as e:
55
+ print(f"POP3 protocol error: {e}")
56
+ return False
57
+ except Exception as e:
58
+ print(f"Error: {e}")
59
+ return False
60
+
61
+
62
+def example_list_messages():
63
+ """Example 2: List all messages in mailbox"""
64
+ print("\n" + "="*50)
65
+ print("EXAMPLE 2: List All Messages")
66
+ print("="*50 + "\n")
67
+
68
+ try:
69
+ pop3_server = poplib.POP3("localhost", 110)
70
+ pop3_server.user("testuser")
71
+ pop3_server.pass_("testpass")
72
+
73
+ # Get mailbox statistics
74
+ message_count, mailbox_size = pop3_server.stat()
75
+ print(f"Mailbox statistics:")
76
+ print(f" Messages: {message_count}")
77
+ print(f" Total size: {mailbox_size} bytes")
78
+
79
+ # List all messages
80
+ print(f"\nListing all messages:")
81
+ response, message_list, octets = pop3_server.list()
82
+ print(f"Response: {response.decode('utf-8')}")
83
+
84
+ for msg in message_list:
85
+ msg_info = msg.decode('utf-8')
86
+ parts = msg_info.split()
87
+ if len(parts) >= 2:
88
+ msg_num, msg_size = parts[0], parts[1]
89
+ print(f" Message {msg_num}: {msg_size} bytes")
90
+
91
+ pop3_server.quit()
92
+ return True
93
+
94
+ except Exception as e:
95
+ print(f"Error: {e}")
96
+ return False
97
+
98
+
99
+def example_retrieve_message():
100
+ """Example 3: Retrieve and parse a message"""
101
+ print("\n" + "="*50)
102
+ print("EXAMPLE 3: Retrieve and Parse Message")
103
+ print("="*50 + "\n")
104
+
105
+ try:
106
+ pop3_server = poplib.POP3("localhost", 110)
107
+ pop3_server.user("testuser")
108
+ pop3_server.pass_("testpass")
109
+
110
+ message_count, _ = pop3_server.stat()
111
+
112
+ if message_count > 0:
113
+ print(f"Retrieving message 1...")
114
+
115
+ # Retrieve message (returns response, lines, octets)
116
+ response, lines, octets = pop3_server.retr(1)
117
+ print(f"Response: {response.decode('utf-8')}")
118
+ print(f"Size: {octets} bytes")
119
+
120
+ # Parse the message
121
+ msg_content = b'\n'.join(lines)
122
+ msg = Parser(policy=default).parsestr(msg_content.decode('utf-8'))
123
+
124
+ print(f"\n--- Message Details ---")
125
+ print(f"From: {msg.get('From', 'N/A')}")
126
+ print(f"To: {msg.get('To', 'N/A')}")
127
+ print(f"Subject: {msg.get('Subject', 'N/A')}")
128
+ print(f"Date: {msg.get('Date', 'N/A')}")
129
+
130
+ # Get message body
131
+ if msg.is_multipart():
132
+ print("\n--- Message Body (multipart) ---")
133
+ for part in msg.walk():
134
+ if part.get_content_type() == "text/plain":
135
+ body = part.get_payload(decode=True)
136
+ if body:
137
+ print(body.decode('utf-8', errors='ignore')[:500])
138
+ break
139
+ else:
140
+ print("\n--- Message Body ---")
141
+ body = msg.get_payload(decode=True)
142
+ if body:
143
+ print(body.decode('utf-8', errors='ignore')[:500])
144
+ else:
145
+ print("No messages in mailbox")
146
+
147
+ pop3_server.quit()
148
+ return True
149
+
150
+ except Exception as e:
151
+ print(f"Error: {e}")
152
+ return False
153
+
154
+
155
+def example_retrieve_all_messages():
156
+ """Example 4: Retrieve all messages"""
157
+ print("\n" + "="*50)
158
+ print("EXAMPLE 4: Retrieve All Messages")
159
+ print("="*50 + "\n")
160
+
161
+ try:
162
+ pop3_server = poplib.POP3("localhost", 110)
163
+ pop3_server.user("testuser")
164
+ pop3_server.pass_("testpass")
165
+
166
+ message_count, _ = pop3_server.stat()
167
+ print(f"Retrieving {message_count} message(s)...\n")
168
+
169
+ for i in range(1, message_count + 1):
170
+ print(f"--- Message {i} ---")
171
+ response, lines, octets = pop3_server.retr(i)
172
+
173
+ msg_content = b'\n'.join(lines)
174
+ msg = Parser(policy=default).parsestr(msg_content.decode('utf-8'))
175
+
176
+ print(f"Subject: {msg.get('Subject', 'N/A')}")
177
+ print(f"From: {msg.get('From', 'N/A')}")
178
+ print(f"Size: {octets} bytes")
179
+ print()
180
+
181
+ pop3_server.quit()
182
+ return True
183
+
184
+ except Exception as e:
185
+ print(f"Error: {e}")
186
+ return False
187
+
188
+
189
+def example_delete_messages():
190
+ """Example 5: Delete messages"""
191
+ print("\n" + "="*50)
192
+ print("EXAMPLE 5: Delete Messages")
193
+ print("="*50 + "\n")
194
+
195
+ try:
196
+ pop3_server = poplib.POP3("localhost", 110)
197
+ pop3_server.user("testuser")
198
+ pop3_server.pass_("testpass")
199
+
200
+ message_count, _ = pop3_server.stat()
201
+
202
+ if message_count > 0:
203
+ print(f"Marking message 1 for deletion...")
204
+ response = pop3_server.dele(1)
205
+ print(f"Response: {response.decode('utf-8')}")
206
+
207
+ print("\nResetting (unmark deletion)...")
208
+ response = pop3_server.rset()
209
+ print(f"Response: {response.decode('utf-8')}")
210
+
211
+ # Verify message still exists
212
+ message_count_after_reset, _ = pop3_server.stat()
213
+ print(f"Messages after reset: {message_count_after_reset}")
214
+
215
+ # Mark for deletion again
216
+ print("\nMarking message 1 for deletion again...")
217
+ response = pop3_server.dele(1)
218
+ print(f"Response: {response.decode('utf-8')}")
219
+
220
+ print("\nNote: Deletion will be committed when we QUIT")
221
+ else:
222
+ print("No messages to delete")
223
+
224
+ pop3_server.quit()
225
+ print("Connection closed - deletion committed")
226
+ return True
227
+
228
+ except Exception as e:
229
+ print(f"Error: {e}")
230
+ return False
231
+
232
+
233
+def example_top_command():
234
+ """Example 6: Use TOP command to get message headers"""
235
+ print("\n" + "="*50)
236
+ print("EXAMPLE 6: TOP Command (Headers Only)")
237
+ print("="*50 + "\n")
238
+
239
+ try:
240
+ pop3_server = poplib.POP3("localhost", 110)
241
+ pop3_server.user("testuser")
242
+ pop3_server.pass_("testpass")
243
+
244
+ message_count, _ = pop3_server.stat()
245
+
246
+ if message_count > 0:
247
+ # Get headers and first 0 lines of body
248
+ print("Retrieving headers of message 1...")
249
+ response, lines, octets = pop3_server.top(1, 0)
250
+ print(f"Response: {response.decode('utf-8')}")
251
+
252
+ print("\n--- Headers ---")
253
+ for line in lines[:10]: # Show first 10 lines
254
+ print(line.decode('utf-8', errors='ignore'))
255
+ else:
256
+ print("No messages in mailbox")
257
+
258
+ pop3_server.quit()
259
+ return True
260
+
261
+ except poplib.error_proto as e:
262
+ print(f"TOP command not supported by server: {e}")
263
+ return False
264
+ except Exception as e:
265
+ print(f"Error: {e}")
266
+ return False
267
+
268
+
269
+def example_uidl_command():
270
+ """Example 7: Use UIDL command for unique message IDs"""
271
+ print("\n" + "="*50)
272
+ print("EXAMPLE 7: UIDL Command (Unique IDs)")
273
+ print("="*50 + "\n")
274
+
275
+ try:
276
+ pop3_server = poplib.POP3("localhost", 110)
277
+ pop3_server.user("testuser")
278
+ pop3_server.pass_("testpass")
279
+
280
+ message_count, _ = pop3_server.stat()
281
+
282
+ if message_count > 0:
283
+ print("Getting unique IDs for all messages...")
284
+ response, uidl_list, octets = pop3_server.uidl()
285
+ print(f"Response: {response.decode('utf-8')}")
286
+
287
+ print("\n--- Unique IDs ---")
288
+ for uidl in uidl_list:
289
+ print(uidl.decode('utf-8'))
290
+ else:
291
+ print("No messages in mailbox")
292
+
293
+ pop3_server.quit()
294
+ return True
295
+
296
+ except poplib.error_proto as e:
297
+ print(f"UIDL command not supported by server: {e}")
298
+ return False
299
+ except Exception as e:
300
+ print(f"Error: {e}")
301
+ return False
302
+
303
+
304
+def example_error_handling():
305
+ """Example 8: Error handling scenarios"""
306
+ print("\n" + "="*50)
307
+ print("EXAMPLE 8: Error Handling")
308
+ print("="*50 + "\n")
309
+
310
+ # Test 1: Wrong credentials
311
+ print("--- Test 1: Authentication with wrong credentials ---")
312
+ try:
313
+ pop3_server = poplib.POP3("localhost", 110)
314
+ pop3_server.user("wronguser")
315
+ pop3_server.pass_("wrongpass")
316
+ pop3_server.quit()
317
+ print("Unexpected: Authentication succeeded")
318
+ except poplib.error_proto as e:
319
+ print(f"Expected error: {e}")
320
+ except Exception as e:
321
+ print(f"Connection error (server not running?): {e}")
322
+
323
+ # Test 2: Retrieve non-existent message
324
+ print("\n--- Test 2: Retrieve non-existent message ---")
325
+ try:
326
+ pop3_server = poplib.POP3("localhost", 110)
327
+ pop3_server.user("testuser")
328
+ pop3_server.pass_("testpass")
329
+
330
+ # Try to retrieve message 99999 (should not exist)
331
+ response, lines, octets = pop3_server.retr(99999)
332
+ print("Unexpected: Retrieved non-existent message")
333
+ pop3_server.quit()
334
+ except poplib.error_proto as e:
335
+ print(f"Expected error: {e}")
336
+ except Exception as e:
337
+ print(f"Connection error: {e}")
338
+
339
+ # Test 3: Connection to wrong port
340
+ print("\n--- Test 3: Connection to wrong port ---")
341
+ try:
342
+ pop3_server = poplib.POP3("localhost", 9999, timeout=2)
343
+ print("Unexpected: Connection succeeded")
344
+ except Exception as e:
345
+ print(f"Expected error: {e}")
346
+
347
+
348
+def example_secure_connection():
349
+ """Example 9: Secure POP3 connection (POP3S)"""
350
+ print("\n" + "="*50)
351
+ print("EXAMPLE 9: Secure POP3 Connection (POP3S)")
352
+ print("="*50 + "\n")
353
+
354
+ try:
355
+ # POP3S typically uses port 995
356
+ print("Connecting to localhost:995 with SSL/TLS...")
357
+ pop3_server = poplib.POP3_SSL("localhost", 995)
358
+
359
+ greeting = pop3_server.getwelcome()
360
+ print(f"Server greeting: {greeting.decode('utf-8')}")
361
+
362
+ pop3_server.user("testuser")
363
+ pop3_server.pass_("testpass")
364
+ print("Secure authentication successful!")
365
+
366
+ pop3_server.quit()
367
+ return True
368
+
369
+ except Exception as e:
370
+ print(f"POP3S connection failed (expected if not configured): {e}")
371
+ return False
372
+
373
+
374
+def main():
375
+ """Run all examples"""
376
+ print("="*60)
377
+ print("POP3 Client Examples in Python")
378
+ print("="*60)
379
+ print("\nThese examples demonstrate POP3 operations using Python's")
380
+ print("poplib library.")
381
+ print("\nNote: Examples assume a POP3 server is running on localhost:110")
382
+ print(" with user 'testuser' and password 'testpass'.")
383
+ print("\nIf the server is not running, you'll see connection errors.")
384
+
385
+ # Run examples
386
+ examples = [
387
+ example_basic_connection,
388
+ example_list_messages,
389
+ example_retrieve_message,
390
+ example_retrieve_all_messages,
391
+ example_delete_messages,
392
+ example_top_command,
393
+ example_uidl_command,
394
+ example_error_handling,
395
+ example_secure_connection,
396
+ ]
397
+
398
+ for example_func in examples:
399
+ try:
400
+ example_func()
401
+ except KeyboardInterrupt:
402
+ print("\n\nInterrupted by user")
403
+ sys.exit(0)
404
+ except Exception as e:
405
+ print(f"Unexpected error in {example_func.__name__}: {e}")
406
+
407
+ print("\n" + "="*60)
408
+ print("All examples completed!")
409
+ print("="*60)
410
+
411
+
412
+if __name__ == "__main__":
413
+ main()
examples/pop3_client.rs
new
+455
@@ -0,0 +1,455 @@
1
+//! POP3 Client Example
2
+//!
3
+//! This example demonstrates how to connect to a POP3 server and perform
4
+//! common operations such as authentication, listing messages, retrieving
5
+//! messages, and deleting messages.
6
+//!
7
+//! Usage:
8
+//! cargo run --example pop3_client
9
+//!
10
+//! This example shows:
11
+//! - Connecting to a POP3 server
12
+//! - Authenticating with USER and PASS commands
13
+//! - Getting mailbox statistics with STAT
14
+//! - Listing messages with LIST
15
+//! - Retrieving messages with RETR
16
+//! - Deleting messages with DELE
17
+//! - Properly closing connection with QUIT
18
+
19
+use std::io::{BufRead, BufReader, Write};
20
+use std::net::TcpStream;
21
+
22
+/// Represents a POP3 client connection
23
+struct Pop3Client {
24
+ stream: TcpStream,
25
+ reader: BufReader<TcpStream>,
26
+}
27
+
28
+impl Pop3Client {
29
+ /// Connect to a POP3 server
30
+ fn connect(host: &str, port: u16) -> Result<Self, Box<dyn std::error::Error>> {
31
+ println!("Connecting to {}:{}...", host, port);
32
+ let stream = TcpStream::connect(format!("{}:{}", host, port))?;
33
+ let reader = BufReader::new(stream.try_clone()?);
34
+
35
+ let mut client = Pop3Client { stream, reader };
36
+
37
+ // Read the server greeting
38
+ let greeting = client.read_response()?;
39
+ println!("Server greeting: {}", greeting);
40
+
41
+ if !greeting.starts_with("+OK") {
42
+ return Err(format!("Server did not send +OK greeting: {}", greeting).into());
43
+ }
44
+
45
+ Ok(client)
46
+ }
47
+
48
+ /// Send a command to the server
49
+ fn send_command(&mut self, command: &str) -> Result<(), Box<dyn std::error::Error>> {
50
+ println!("> {}", command);
51
+ writeln!(self.stream, "{}", command)?;
52
+ self.stream.flush()?;
53
+ Ok(())
54
+ }
55
+
56
+ /// Read a single-line response from the server
57
+ fn read_response(&mut self) -> Result<String, Box<dyn std::error::Error>> {
58
+ let mut response = String::new();
59
+ self.reader.read_line(&mut response)?;
60
+ let response = response.trim_end().to_string();
61
+ println!("< {}", response);
62
+ Ok(response)
63
+ }
64
+
65
+ /// Read a multi-line response from the server (ends with ".")
66
+ fn read_multiline_response(&mut self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
67
+ let mut lines = Vec::new();
68
+ loop {
69
+ let mut line = String::new();
70
+ self.reader.read_line(&mut line)?;
71
+ let line = line.trim_end().to_string();
72
+ println!("< {}", line);
73
+
74
+ if line == "." {
75
+ break;
76
+ }
77
+ lines.push(line);
78
+ }
79
+ Ok(lines)
80
+ }
81
+
82
+ /// Authenticate with username and password
83
+ fn authenticate(&mut self, username: &str, password: &str) -> Result<(), Box<dyn std::error::Error>> {
84
+ println!("\n=== Authenticating ===");
85
+
86
+ // Send USER command
87
+ self.send_command(&format!("USER {}", username))?;
88
+ let response = self.read_response()?;
89
+ if !response.starts_with("+OK") {
90
+ return Err(format!("USER command failed: {}", response).into());
91
+ }
92
+
93
+ // Send PASS command
94
+ self.send_command(&format!("PASS {}", password))?;
95
+ let response = self.read_response()?;
96
+ if !response.starts_with("+OK") {
97
+ return Err(format!("PASS command failed: {}", response).into());
98
+ }
99
+
100
+ println!("Authentication successful!");
101
+ Ok(())
102
+ }
103
+
104
+ /// Get mailbox statistics (STAT command)
105
+ fn stat(&mut self) -> Result<(usize, usize), Box<dyn std::error::Error>> {
106
+ println!("\n=== Getting mailbox statistics ===");
107
+
108
+ self.send_command("STAT")?;
109
+ let response = self.read_response()?;
110
+
111
+ if !response.starts_with("+OK") {
112
+ return Err(format!("STAT command failed: {}", response).into());
113
+ }
114
+
115
+ // Parse response: +OK <message_count> <mailbox_size>
116
+ let parts: Vec<&str> = response.split_whitespace().collect();
117
+ if parts.len() >= 3 {
118
+ let message_count = parts[1].parse::<usize>()?;
119
+ let mailbox_size = parts[2].parse::<usize>()?;
120
+ println!("Mailbox has {} messages ({} bytes)", message_count, mailbox_size);
121
+ Ok((message_count, mailbox_size))
122
+ } else {
123
+ Err("Invalid STAT response format".into())
124
+ }
125
+ }
126
+
127
+ /// List all messages (LIST command)
128
+ fn list(&mut self) -> Result<Vec<(usize, usize)>, Box<dyn std::error::Error>> {
129
+ println!("\n=== Listing all messages ===");
130
+
131
+ self.send_command("LIST")?;
132
+ let response = self.read_response()?;
133
+
134
+ if !response.starts_with("+OK") {
135
+ return Err(format!("LIST command failed: {}", response).into());
136
+ }
137
+
138
+ let lines = self.read_multiline_response()?;
139
+ let mut messages = Vec::new();
140
+
141
+ for line in lines {
142
+ let parts: Vec<&str> = line.split_whitespace().collect();
143
+ if parts.len() >= 2 {
144
+ let msg_num = parts[0].parse::<usize>()?;
145
+ let msg_size = parts[1].parse::<usize>()?;
146
+ messages.push((msg_num, msg_size));
147
+ println!(" Message {}: {} bytes", msg_num, msg_size);
148
+ }
149
+ }
150
+
151
+ Ok(messages)
152
+ }
153
+
154
+ /// List a specific message (LIST command with message number)
155
+ fn list_message(&mut self, msg_num: usize) -> Result<usize, Box<dyn std::error::Error>> {
156
+ println!("\n=== Listing message {} ===", msg_num);
157
+
158
+ self.send_command(&format!("LIST {}", msg_num))?;
159
+ let response = self.read_response()?;
160
+
161
+ if !response.starts_with("+OK") {
162
+ return Err(format!("LIST command failed: {}", response).into());
163
+ }
164
+
165
+ // Parse response: +OK <msg_num> <msg_size>
166
+ let parts: Vec<&str> = response.split_whitespace().collect();
167
+ if parts.len() >= 3 {
168
+ let msg_size = parts[2].parse::<usize>()?;
169
+ println!("Message {} size: {} bytes", msg_num, msg_size);
170
+ Ok(msg_size)
171
+ } else {
172
+ Err("Invalid LIST response format".into())
173
+ }
174
+ }
175
+
176
+ /// Retrieve a message (RETR command)
177
+ fn retrieve(&mut self, msg_num: usize) -> Result<String, Box<dyn std::error::Error>> {
178
+ println!("\n=== Retrieving message {} ===", msg_num);
179
+
180
+ self.send_command(&format!("RETR {}", msg_num))?;
181
+ let response = self.read_response()?;
182
+
183
+ if !response.starts_with("+OK") {
184
+ return Err(format!("RETR command failed: {}", response).into());
185
+ }
186
+
187
+ let lines = self.read_multiline_response()?;
188
+ let message = lines.join("\n");
189
+ println!("Retrieved message ({} lines)", lines.len());
190
+
191
+ Ok(message)
192
+ }
193
+
194
+ /// Delete a message (DELE command)
195
+ fn delete(&mut self, msg_num: usize) -> Result<(), Box<dyn std::error::Error>> {
196
+ println!("\n=== Deleting message {} ===", msg_num);
197
+
198
+ self.send_command(&format!("DELE {}", msg_num))?;
199
+ let response = self.read_response()?;
200
+
201
+ if !response.starts_with("+OK") {
202
+ return Err(format!("DELE command failed: {}", response).into());
203
+ }
204
+
205
+ println!("Message {} marked for deletion", msg_num);
206
+ Ok(())
207
+ }
208
+
209
+ /// Reset the session (RSET command) - unmarks deleted messages
210
+ fn reset(&mut self) -> Result<(), Box<dyn std::error::Error>> {
211
+ println!("\n=== Resetting session ===");
212
+
213
+ self.send_command("RSET")?;
214
+ let response = self.read_response()?;
215
+
216
+ if !response.starts_with("+OK") {
217
+ return Err(format!("RSET command failed: {}", response).into());
218
+ }
219
+
220
+ println!("Session reset - all delete marks removed");
221
+ Ok(())
222
+ }
223
+
224
+ /// No-op command (NOOP) - keeps connection alive
225
+ fn noop(&mut self) -> Result<(), Box<dyn std::error::Error>> {
226
+ println!("\n=== Sending NOOP ===");
227
+
228
+ self.send_command("NOOP")?;
229
+ let response = self.read_response()?;
230
+
231
+ if !response.starts_with("+OK") {
232
+ return Err(format!("NOOP command failed: {}", response).into());
233
+ }
234
+
235
+ println!("NOOP successful");
236
+ Ok(())
237
+ }
238
+
239
+ /// Close the connection (QUIT command)
240
+ fn quit(&mut self) -> Result<(), Box<dyn std::error::Error>> {
241
+ println!("\n=== Closing connection ===");
242
+
243
+ self.send_command("QUIT")?;
244
+ let response = self.read_response()?;
245
+
246
+ if !response.starts_with("+OK") {
247
+ return Err(format!("QUIT command failed: {}", response).into());
248
+ }
249
+
250
+ println!("Connection closed successfully");
251
+ Ok(())
252
+ }
253
+}
254
+
255
+/// Example 1: Complete email retrieval session
256
+fn example_complete_session() -> Result<(), Box<dyn std::error::Error>> {
257
+ println!("\n========================================");
258
+ println!("EXAMPLE 1: Complete Email Retrieval Session");
259
+ println!("========================================\n");
260
+
261
+ let mut client = Pop3Client::connect("localhost", 110)?;
262
+
263
+ // Authenticate
264
+ client.authenticate("testuser", "testpass")?;
265
+
266
+ // Get mailbox statistics
267
+ let (message_count, _mailbox_size) = client.stat()?;
268
+
269
+ // List all messages
270
+ let _messages = client.list()?;
271
+
272
+ // Retrieve first message if available
273
+ if message_count > 0 {
274
+ let message_content = client.retrieve(1)?;
275
+ println!("\nFirst message preview:");
276
+ println!("---");
277
+ for (i, line) in message_content.lines().take(10).enumerate() {
278
+ println!("{}", line);
279
+ if i == 9 && message_content.lines().count() > 10 {
280
+ println!("... (truncated)");
281
+ }
282
+ }
283
+ println!("---");
284
+ }
285
+
286
+ // Close connection
287
+ client.quit()?;
288
+
289
+ Ok(())
290
+}
291
+
292
+/// Example 2: Handling multiple messages
293
+fn example_multiple_messages() -> Result<(), Box<dyn std::error::Error>> {
294
+ println!("\n========================================");
295
+ println!("EXAMPLE 2: Handling Multiple Messages");
296
+ println!("========================================\n");
297
+
298
+ let mut client = Pop3Client::connect("localhost", 110)?;
299
+ client.authenticate("testuser", "testpass")?;
300
+
301
+ // Get all messages
302
+ let messages = client.list()?;
303
+
304
+ println!("\nProcessing {} messages:", messages.len());
305
+ for (msg_num, msg_size) in messages {
306
+ println!("\nProcessing message {} ({} bytes):", msg_num, msg_size);
307
+
308
+ // Retrieve each message
309
+ match client.retrieve(msg_num) {
310
+ Ok(content) => {
311
+ println!(" Successfully retrieved {} bytes", content.len());
312
+ // Process message content here
313
+ }
314
+ Err(e) => {
315
+ println!(" Error retrieving message: {}", e);
316
+ }
317
+ }
318
+ }
319
+
320
+ client.quit()?;
321
+ Ok(())
322
+}
323
+
324
+/// Example 3: Message deletion workflow
325
+fn example_deletion_workflow() -> Result<(), Box<dyn std::error::Error>> {
326
+ println!("\n========================================");
327
+ println!("EXAMPLE 3: Message Deletion Workflow");
328
+ println!("========================================\n");
329
+
330
+ let mut client = Pop3Client::connect("localhost", 110)?;
331
+ client.authenticate("testuser", "testpass")?;
332
+
333
+ let (message_count, _) = client.stat()?;
334
+
335
+ if message_count > 0 {
336
+ // Mark first message for deletion
337
+ client.delete(1)?;
338
+
339
+ // Change our mind - reset to unmark
340
+ client.reset()?;
341
+
342
+ // Verify message is still there
343
+ let messages = client.list()?;
344
+ println!("\nAfter reset, mailbox still has {} messages", messages.len());
345
+
346
+ // Delete again (this time for real)
347
+ client.delete(1)?;
348
+
349
+ // Quit to commit the deletion
350
+ println!("\nNote: Deletion will be committed when we QUIT");
351
+ } else {
352
+ println!("No messages to delete");
353
+ }
354
+
355
+ client.quit()?;
356
+ Ok(())
357
+}
358
+
359
+/// Example 4: Error handling
360
+fn example_error_handling() -> Result<(), Box<dyn std::error::Error>> {
361
+ println!("\n========================================");
362
+ println!("EXAMPLE 4: Error Handling");
363
+ println!("========================================\n");
364
+
365
+ // Example: Authentication failure
366
+ println!("--- Testing authentication failure ---");
367
+ match Pop3Client::connect("localhost", 110) {
368
+ Ok(mut client) => {
369
+ match client.authenticate("wronguser", "wrongpass") {
370
+ Ok(_) => println!("Unexpected: Authentication succeeded"),
371
+ Err(e) => println!("Expected error: {}", e),
372
+ }
373
+ let _ = client.quit();
374
+ }
375
+ Err(e) => {
376
+ println!("Connection failed (expected if server not running): {}", e);
377
+ }
378
+ }
379
+
380
+ // Example: Invalid message number
381
+ println!("\n--- Testing invalid message number ---");
382
+ match Pop3Client::connect("localhost", 110) {
383
+ Ok(mut client) => {
384
+ if client.authenticate("testuser", "testpass").is_ok() {
385
+ match client.retrieve(99999) {
386
+ Ok(_) => println!("Unexpected: Retrieved non-existent message"),
387
+ Err(e) => println!("Expected error: {}", e),
388
+ }
389
+ let _ = client.quit();
390
+ }
391
+ }
392
+ Err(e) => {
393
+ println!("Connection failed (expected if server not running): {}", e);
394
+ }
395
+ }
396
+
397
+ Ok(())
398
+}
399
+
400
+/// Example 5: Connection error scenarios
401
+fn example_connection_errors() {
402
+ println!("\n========================================");
403
+ println!("EXAMPLE 5: Connection Error Scenarios");
404
+ println!("========================================\n");
405
+
406
+ // Try to connect to non-existent server
407
+ println!("--- Testing connection to non-existent server ---");
408
+ match Pop3Client::connect("localhost", 9999) {
409
+ Ok(_) => println!("Unexpected: Connection succeeded"),
410
+ Err(e) => println!("Expected error: {}", e),
411
+ }
412
+
413
+ // Try to connect with invalid hostname
414
+ println!("\n--- Testing connection to invalid hostname ---");
415
+ match Pop3Client::connect("invalid.example.nonexistent", 110) {
416
+ Ok(_) => println!("Unexpected: Connection succeeded"),
417
+ Err(e) => println!("Expected error: {}", e),
418
+ }
419
+}
420
+
421
+fn main() {
422
+ println!("===========================================");
423
+ println!("POP3 Client Examples");
424
+ println!("===========================================");
425
+ println!("\nThis example demonstrates various POP3 operations.");
426
+ println!("Note: These examples assume a POP3 server is running on localhost:110");
427
+ println!(" with user 'testuser' and password 'testpass'.");
428
+ println!("\nIf the server is not running, you'll see connection errors,");
429
+ println!("which is normal and demonstrates error handling.\n");
430
+
431
+ // Run all examples
432
+ // Note: In real usage, you'd typically only run one session at a time
433
+
434
+ if let Err(e) = example_complete_session() {
435
+ println!("Example 1 error: {}", e);
436
+ }
437
+
438
+ if let Err(e) = example_multiple_messages() {
439
+ println!("Example 2 error: {}", e);
440
+ }
441
+
442
+ if let Err(e) = example_deletion_workflow() {
443
+ println!("Example 3 error: {}", e);
444
+ }
445
+
446
+ if let Err(e) = example_error_handling() {
447
+ println!("Example 4 error: {}", e);
448
+ }
449
+
450
+ example_connection_errors();
451
+
452
+ println!("\n===========================================");
453
+ println!("Examples completed!");
454
+ println!("===========================================");
455
+}
examples/pop3_integration_test.rs
new
+455
@@ -0,0 +1,455 @@
1
+//! POP3 Integration Test Example
2
+//!
3
+//! This example demonstrates how to write integration tests for POP3 servers
4
+//! and clients, showing best practices for testing mail systems.
5
+//!
6
+//! Usage:
7
+//! cargo run --example pop3_integration_test
8
+//!
9
+//! This example shows:
10
+//! - Setting up test fixtures
11
+//! - Testing complete POP3 workflows
12
+//! - Mocking server responses
13
+//! - Testing error conditions
14
+//! - Integration between SMTP and POP3
15
+
16
+use std::io::{BufRead, BufReader, Write};
17
+use std::net::{TcpListener, TcpStream};
18
+use std::sync::{Arc, Mutex};
19
+use std::thread;
20
+use std::time::Duration;
21
+
22
+/// Test result
23
+type TestResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;
24
+
25
+/// Simple POP3 test client
26
+struct TestPop3Client {
27
+ stream: TcpStream,
28
+ reader: BufReader<TcpStream>,
29
+}
30
+
31
+impl TestPop3Client {
32
+ fn connect(addr: &str) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
33
+ let stream = TcpStream::connect(addr)?;
34
+ stream.set_read_timeout(Some(Duration::from_secs(5)))?;
35
+ let reader = BufReader::new(stream.try_clone()?);
36
+
37
+ let mut client = TestPop3Client { stream, reader };
38
+ let greeting = client.read_response()?;
39
+
40
+ if !greeting.starts_with("+OK") {
41
+ return Err(format!("Invalid greeting: {}", greeting).into());
42
+ }
43
+
44
+ Ok(client)
45
+ }
46
+
47
+ fn send_command(&mut self, cmd: &str) -> TestResult {
48
+ writeln!(self.stream, "{}", cmd)?;
49
+ self.stream.flush()?;
50
+ Ok(())
51
+ }
52
+
53
+ fn read_response(&mut self) -> Result<String, Box<dyn std::error::Error + Send + Sync>> {
54
+ let mut response = String::new();
55
+ self.reader.read_line(&mut response)?;
56
+ Ok(response.trim_end().to_string())
57
+ }
58
+
59
+ fn read_multiline(&mut self) -> Result<Vec<String>, Box<dyn std::error::Error + Send + Sync>> {
60
+ let mut lines = Vec::new();
61
+ loop {
62
+ let mut line = String::new();
63
+ self.reader.read_line(&mut line)?;
64
+ let line = line.trim_end().to_string();
65
+ if line == "." {
66
+ break;
67
+ }
68
+ lines.push(line);
69
+ }
70
+ Ok(lines)
71
+ }
72
+}
73
+
74
+/// Mock POP3 server for testing
75
+struct MockPop3Server {
76
+ listener: TcpListener,
77
+ running: Arc<Mutex<bool>>,
78
+}
79
+
80
+impl MockPop3Server {
81
+ fn new(port: u16) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
82
+ let listener = TcpListener::bind(format!("127.0.0.1:{}", port))?;
83
+ listener.set_nonblocking(true)?;
84
+
85
+ Ok(MockPop3Server {
86
+ listener,
87
+ running: Arc::new(Mutex::new(true)),
88
+ })
89
+ }
90
+
91
+ fn start(&self) -> TestResult {
92
+ let listener = self.listener.try_clone()?;
93
+ let running = Arc::clone(&self.running);
94
+
95
+ thread::spawn(move || {
96
+ while *running.lock().unwrap() {
97
+ match listener.accept() {
98
+ Ok((mut stream, _)) => {
99
+ let _ = Self::handle_client(&mut stream);
100
+ }
101
+ Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
102
+ thread::sleep(Duration::from_millis(10));
103
+ }
104
+ Err(_) => break,
105
+ }
106
+ }
107
+ });
108
+
109
+ thread::sleep(Duration::from_millis(100));
110
+ Ok(())
111
+ }
112
+
113
+ fn handle_client(stream: &mut TcpStream) -> TestResult {
114
+ writeln!(stream, "+OK Mock POP3 server ready")?;
115
+ stream.flush()?;
116
+
117
+ let mut reader = BufReader::new(stream.try_clone()?);
118
+
119
+ loop {
120
+ let mut line = String::new();
121
+ if reader.read_line(&mut line).is_err() {
122
+ break;
123
+ }
124
+
125
+ let cmd = line.trim().to_uppercase();
126
+
127
+ let response = match cmd.as_str() {
128
+ s if s.starts_with("USER") => "+OK User accepted",
129
+ s if s.starts_with("PASS") => "+OK Password accepted",
130
+ "STAT" => "+OK 2 640",
131
+ "LIST" => "+OK 2 messages (640 octets)\r\n1 320\r\n2 320\r\n.",
132
+ s if s.starts_with("RETR 1") => {
133
+ "+OK 320 octets\r\nFrom: test@example.com\r\nSubject: Test\r\n\r\nTest body\r\n."
134
+ }
135
+ s if s.starts_with("DELE") => "+OK Message deleted",
136
+ "RSET" => "+OK Reset",
137
+ "NOOP" => "+OK",
138
+ "QUIT" => {
139
+ writeln!(stream, "+OK Bye")?;
140
+ stream.flush()?;
141
+ break;
142
+ }
143
+ _ => "-ERR Unknown command",
144
+ };
145
+
146
+ writeln!(stream, "{}", response)?;
147
+ stream.flush()?;
148
+ }
149
+
150
+ Ok(())
151
+ }
152
+
153
+ fn stop(&self) {
154
+ *self.running.lock().unwrap() = false;
155
+ }
156
+}
157
+
158
+/// Test 1: Basic connection and authentication
159
+fn test_basic_authentication() -> TestResult {
160
+ println!("\n=== Test 1: Basic Authentication ===");
161
+
162
+ let server = MockPop3Server::new(11001)?;
163
+ server.start()?;
164
+
165
+ thread::sleep(Duration::from_millis(100));
166
+
167
+ let mut client = TestPop3Client::connect("127.0.0.1:11001")?;
168
+
169
+ client.send_command("USER testuser")?;
170
+ let response = client.read_response()?;
171
+ assert!(response.starts_with("+OK"), "USER failed: {}", response);
172
+
173
+ client.send_command("PASS testpass")?;
174
+ let response = client.read_response()?;
175
+ assert!(response.starts_with("+OK"), "PASS failed: {}", response);
176
+
177
+ client.send_command("QUIT")?;
178
+ let _ = client.read_response();
179
+
180
+ server.stop();
181
+
182
+ println!("✓ Authentication test passed");
183
+ Ok(())
184
+}
185
+
186
+/// Test 2: Message listing and retrieval
187
+fn test_message_operations() -> TestResult {
188
+ println!("\n=== Test 2: Message Operations ===");
189
+
190
+ let server = MockPop3Server::new(11002)?;
191
+ server.start()?;
192
+
193
+ thread::sleep(Duration::from_millis(100));
194
+
195
+ let mut client = TestPop3Client::connect("127.0.0.1:11002")?;
196
+
197
+ // Authenticate
198
+ client.send_command("USER testuser")?;
199
+ let _ = client.read_response()?;
200
+ client.send_command("PASS testpass")?;
201
+ let _ = client.read_response()?;
202
+
203
+ // Get statistics
204
+ client.send_command("STAT")?;
205
+ let response = client.read_response()?;
206
+ assert!(response.contains("2 640"), "STAT failed: {}", response);
207
+
208
+ // List messages
209
+ client.send_command("LIST")?;
210
+ let _ = client.read_response()?;
211
+ let messages = client.read_multiline()?;
212
+ assert!(messages.len() >= 2, "LIST failed: expected 2 messages");
213
+
214
+ // Retrieve message
215
+ client.send_command("RETR 1")?;
216
+ let _ = client.read_response()?;
217
+ let content = client.read_multiline()?;
218
+ assert!(!content.is_empty(), "RETR failed: empty message");
219
+
220
+ client.send_command("QUIT")?;
221
+ let _ = client.read_response();
222
+
223
+ server.stop();
224
+
225
+ println!("✓ Message operations test passed");
226
+ Ok(())
227
+}
228
+
229
+/// Test 3: Message deletion workflow
230
+fn test_deletion_workflow() -> TestResult {
231
+ println!("\n=== Test 3: Deletion Workflow ===");
232
+
233
+ let server = MockPop3Server::new(11003)?;
234
+ server.start()?;
235
+
236
+ thread::sleep(Duration::from_millis(100));
237
+
238
+ let mut client = TestPop3Client::connect("127.0.0.1:11003")?;
239
+
240
+ // Authenticate
241
+ client.send_command("USER testuser")?;
242
+ let _ = client.read_response()?;
243
+ client.send_command("PASS testpass")?;
244
+ let _ = client.read_response()?;
245
+
246
+ // Delete message
247
+ client.send_command("DELE 1")?;
248
+ let response = client.read_response()?;
249
+ assert!(response.starts_with("+OK"), "DELE failed: {}", response);
250
+
251
+ // Reset
252
+ client.send_command("RSET")?;
253
+ let response = client.read_response()?;
254
+ assert!(response.starts_with("+OK"), "RSET failed: {}", response);
255
+
256
+ // Delete again
257
+ client.send_command("DELE 1")?;
258
+ let response = client.read_response()?;
259
+ assert!(response.starts_with("+OK"), "DELE failed: {}", response);
260
+
261
+ client.send_command("QUIT")?;
262
+ let _ = client.read_response();
263
+
264
+ server.stop();
265
+
266
+ println!("✓ Deletion workflow test passed");
267
+ Ok(())
268
+}
269
+
270
+/// Test 4: Error handling
271
+fn test_error_handling() -> TestResult {
272
+ println!("\n=== Test 4: Error Handling ===");
273
+
274
+ // Test connection to non-existent server
275
+ println!(" Testing connection to non-existent server...");
276
+ match TcpStream::connect_timeout(
277
+ &"127.0.0.1:9999".parse().unwrap(),
278
+ Duration::from_secs(1)
279
+ ) {
280
+ Ok(_) => return Err("Should have failed to connect".into()),
281
+ Err(_) => println!(" ✓ Connection refused as expected"),
282
+ }
283
+
284
+ println!("✓ Error handling test passed");
285
+ Ok(())
286
+}
287
+
288
+/// Test 5: Complete session workflow
289
+fn test_complete_session() -> TestResult {
290
+ println!("\n=== Test 5: Complete Session Workflow ===");
291
+
292
+ let server = MockPop3Server::new(11005)?;
293
+ server.start()?;
294
+
295
+ thread::sleep(Duration::from_millis(100));
296
+
297
+ let mut client = TestPop3Client::connect("127.0.0.1:11005")?;
298
+
299
+ // Full workflow
300
+ let commands = vec![
301
+ ("USER testuser", "+OK"),
302
+ ("PASS testpass", "+OK"),
303
+ ("STAT", "+OK"),
304
+ ("LIST", "+OK"),
305
+ ("NOOP", "+OK"),
306
+ ("QUIT", "+OK"),
307
+ ];
308
+
309
+ for (cmd, expected) in commands {
310
+ client.send_command(cmd)?;
311
+ let response = client.read_response()?;
312
+
313
+ // Handle multiline responses
314
+ if cmd == "LIST" {
315
+ let _ = client.read_multiline()?;
316
+ }
317
+
318
+ assert!(
319
+ response.starts_with(expected),
320
+ "{} failed: expected '{}', got '{}'",
321
+ cmd, expected, response
322
+ );
323
+
324
+ if cmd == "QUIT" {
325
+ break;
326
+ }
327
+ }
328
+
329
+ server.stop();
330
+
331
+ println!("✓ Complete session test passed");
332
+ Ok(())
333
+}
334
+
335
+/// Test 6: Concurrent connections
336
+fn test_concurrent_connections() -> TestResult {
337
+ println!("\n=== Test 6: Concurrent Connections ===");
338
+
339
+ let server = MockPop3Server::new(11006)?;
340
+ server.start()?;
341
+
342
+ thread::sleep(Duration::from_millis(100));
343
+
344
+ let mut handles = vec![];
345
+
346
+ for i in 0..5 {
347
+ let handle = thread::spawn(move || -> TestResult {
348
+ let mut client = TestPop3Client::connect("127.0.0.1:11006")?;
349
+
350
+ client.send_command("USER testuser")?;
351
+ let _ = client.read_response()?;
352
+ client.send_command("PASS testpass")?;
353
+ let _ = client.read_response()?;
354
+ client.send_command("STAT")?;
355
+ let _ = client.read_response()?;
356
+ client.send_command("QUIT")?;
357
+ let _ = client.read_response();
358
+
359
+ println!(" ✓ Client {} completed", i + 1);
360
+ Ok(())
361
+ });
362
+ handles.push(handle);
363
+ }
364
+
365
+ for handle in handles {
366
+ handle.join().unwrap()?;
367
+ }
368
+
369
+ server.stop();
370
+
371
+ println!("✓ Concurrent connections test passed");
372
+ Ok(())
373
+}
374
+
375
+/// Test 7: Command sequencing
376
+fn test_command_sequencing() -> TestResult {
377
+ println!("\n=== Test 7: Command Sequencing ===");
378
+
379
+ let server = MockPop3Server::new(11007)?;
380
+ server.start()?;
381
+
382
+ thread::sleep(Duration::from_millis(100));
383
+
384
+ let mut client = TestPop3Client::connect("127.0.0.1:11007")?;
385
+
386
+ // Test proper sequence
387
+ client.send_command("USER testuser")?;
388
+ let _ = client.read_response()?;
389
+ client.send_command("PASS testpass")?;
390
+ let _ = client.read_response()?;
391
+
392
+ // Commands that require authentication
393
+ let commands = vec!["STAT", "LIST", "NOOP"];
394
+ for cmd in commands {
395
+ client.send_command(cmd)?;
396
+ let response = client.read_response()?;
397
+ if cmd == "LIST" {
398
+ let _ = client.read_multiline()?;
399
+ }
400
+ assert!(response.starts_with("+OK"), "{} failed: {}", cmd, response);
401
+ }
402
+
403
+ client.send_command("QUIT")?;
404
+ let _ = client.read_response();
405
+
406
+ server.stop();
407
+
408
+ println!("✓ Command sequencing test passed");
409
+ Ok(())
410
+}
411
+
412
+fn main() {
413
+ println!("========================================");
414
+ println!("POP3 Integration Test Examples");
415
+ println!("========================================");
416
+ println!("\nRunning comprehensive integration tests...\n");
417
+
418
+ let tests: Vec<(&str, fn() -> TestResult)> = vec![
419
+ ("Basic Authentication", test_basic_authentication),
420
+ ("Message Operations", test_message_operations),
421
+ ("Deletion Workflow", test_deletion_workflow),
422
+ ("Error Handling", test_error_handling),
423
+ ("Complete Session", test_complete_session),
424
+ ("Concurrent Connections", test_concurrent_connections),
425
+ ("Command Sequencing", test_command_sequencing),
426
+ ];
427
+
428
+ let mut passed = 0;
429
+ let mut failed = 0;
430
+
431
+ for (name, test_fn) in tests {
432
+ match test_fn() {
433
+ Ok(_) => {
434
+ passed += 1;
435
+ }
436
+ Err(e) => {
437
+ println!("✗ Test '{}' failed: {}", name, e);
438
+ failed += 1;
439
+ }
440
+ }
441
+ }
442
+
443
+ println!("\n========================================");
444
+ println!("Test Results");
445
+ println!("========================================");
446
+ println!("Passed: {}", passed);
447
+ println!("Failed: {}", failed);
448
+ println!("Total: {}", passed + failed);
449
+
450
+ if failed == 0 {
451
+ println!("\n🎉 All tests passed!");
452
+ } else {
453
+ println!("\n⚠️ Some tests failed!");
454
+ }
455
+}
examples/pop3_server.rs
new
+432
@@ -0,0 +1,432 @@
1
+//! POP3 Server Example
2
+//!
3
+//! This example demonstrates how to set up and run a basic POP3 server
4
+//! that can handle client connections, authentication, and message operations.
5
+//!
6
+//! Usage:
7
+//! cargo run --example pop3_server
8
+//!
9
+//! This example shows:
10
+//! - Setting up a POP3 server
11
+//! - Handling client connections
12
+//! - Processing POP3 commands
13
+//! - Managing mailbox state
14
+//! - Proper error handling
15
+
16
+use std::collections::HashMap;
17
+use std::io::{BufRead, BufReader, Write};
18
+use std::net::{TcpListener, TcpStream};
19
+use std::sync::{Arc, Mutex};
20
+use std::thread;
21
+
22
+/// Represents a mail message
23
+#[derive(Clone, Debug)]
24
+struct Message {
25
+ id: usize,
26
+ size: usize,
27
+ content: String,
28
+ deleted: bool,
29
+}
30
+
31
+/// User mailbox
32
+#[derive(Clone, Debug)]
33
+struct Mailbox {
34
+ messages: Vec<Message>,
35
+}
36
+
37
+impl Mailbox {
38
+ fn new() -> Self {
39
+ Mailbox {
40
+ messages: Vec::new(),
41
+ }
42
+ }
43
+
44
+ fn add_sample_messages(&mut self) {
45
+ // Add some sample messages for testing
46
+ self.messages.push(Message {
47
+ id: 1,
48
+ size: 250,
49
+ content: "From: sender1@example.com\r\nTo: user@example.com\r\nSubject: Test Message 1\r\n\r\nThis is the first test message.\r\n".to_string(),
50
+ deleted: false,
51
+ });
52
+
53
+ self.messages.push(Message {
54
+ id: 2,
55
+ size: 300,
56
+ content: "From: sender2@example.com\r\nTo: user@example.com\r\nSubject: Test Message 2\r\n\r\nThis is the second test message with more content.\r\n".to_string(),
57
+ deleted: false,
58
+ });
59
+
60
+ self.messages.push(Message {
61
+ id: 3,
62
+ size: 180,
63
+ content: "From: sender3@example.com\r\nTo: user@example.com\r\nSubject: Short Message\r\n\r\nBrief message.\r\n".to_string(),
64
+ deleted: false,
65
+ });
66
+ }
67
+
68
+ fn stat(&self) -> (usize, usize) {
69
+ let count = self.messages.iter().filter(|m| !m.deleted).count();
70
+ let size = self.messages.iter()
71
+ .filter(|m| !m.deleted)
72
+ .map(|m| m.size)
73
+ .sum();
74
+ (count, size)
75
+ }
76
+
77
+ fn list_all(&self) -> Vec<(usize, usize)> {
78
+ self.messages.iter()
79
+ .filter(|m| !m.deleted)
80
+ .map(|m| (m.id, m.size))
81
+ .collect()
82
+ }
83
+
84
+ fn get_message(&self, id: usize) -> Option<&Message> {
85
+ self.messages.iter()
86
+ .find(|m| m.id == id && !m.deleted)
87
+ }
88
+
89
+ fn delete_message(&mut self, id: usize) -> bool {
90
+ if let Some(msg) = self.messages.iter_mut().find(|m| m.id == id && !m.deleted) {
91
+ msg.deleted = true;
92
+ true
93
+ } else {
94
+ false
95
+ }
96
+ }
97
+
98
+ fn reset(&mut self) {
99
+ for msg in &mut self.messages {
100
+ msg.deleted = false;
101
+ }
102
+ }
103
+
104
+ fn commit_deletions(&mut self) {
105
+ self.messages.retain(|m| !m.deleted);
106
+ }
107
+}
108
+
109
+/// POP3 server state
110
+struct Pop3Server {
111
+ users: Arc<Mutex<HashMap<String, String>>>, // username -> password
112
+ mailboxes: Arc<Mutex<HashMap<String, Mailbox>>>, // username -> mailbox
113
+}
114
+
115
+impl Pop3Server {
116
+ fn new() -> Self {
117
+ let mut users = HashMap::new();
118
+ users.insert("testuser".to_string(), "testpass".to_string());
119
+ users.insert("alice".to_string(), "password123".to_string());
120
+ users.insert("bob".to_string(), "secret".to_string());
121
+
122
+ let mut mailboxes = HashMap::new();
123
+
124
+ // Create sample mailbox for testuser
125
+ let mut testuser_mailbox = Mailbox::new();
126
+ testuser_mailbox.add_sample_messages();
127
+ mailboxes.insert("testuser".to_string(), testuser_mailbox);
128
+
129
+ // Create sample mailbox for alice
130
+ let mut alice_mailbox = Mailbox::new();
131
+ alice_mailbox.add_sample_messages();
132
+ mailboxes.insert("alice".to_string(), alice_mailbox);
133
+
134
+ // Create empty mailbox for bob
135
+ mailboxes.insert("bob".to_string(), Mailbox::new());
136
+
137
+ Pop3Server {
138
+ users: Arc::new(Mutex::new(users)),
139
+ mailboxes: Arc::new(Mutex::new(mailboxes)),
140
+ }
141
+ }
142
+
143
+ fn handle_client(&self, mut stream: TcpStream) {
144
+ println!("New client connected from {}", stream.peer_addr().unwrap());
145
+
146
+ // Send greeting
147
+ if let Err(e) = writeln!(stream, "+OK POP3 server ready") {
148
+ eprintln!("Error sending greeting: {}", e);
149
+ return;
150
+ }
151
+
152
+ let mut reader = BufReader::new(stream.try_clone().unwrap());
153
+ let mut authenticated_user: Option<String> = None;
154
+ let mut pending_user: Option<String> = None;
155
+
156
+ loop {
157
+ let mut line = String::new();
158
+ match reader.read_line(&mut line) {
159
+ Ok(0) => break, // Connection closed
160
+ Ok(_) => {
161
+ let line = line.trim();
162
+ println!("< {}", line);
163
+
164
+ let parts: Vec<&str> = line.splitn(2, ' ').collect();
165
+ let command = parts[0].to_uppercase();
166
+ let args = if parts.len() > 1 { parts[1] } else { "" };
167
+
168
+ let response = match command.as_str() {
169
+ "USER" => self.handle_user(args, &mut pending_user),
170
+ "PASS" => self.handle_pass(args, &pending_user, &mut authenticated_user),
171
+ "STAT" => self.handle_stat(&authenticated_user),
172
+ "LIST" => self.handle_list(&authenticated_user, args),
173
+ "RETR" => self.handle_retr(&authenticated_user, args),
174
+ "DELE" => self.handle_dele(&authenticated_user, args),
175
+ "NOOP" => self.handle_noop(&authenticated_user),
176
+ "RSET" => self.handle_rset(&authenticated_user),
177
+ "QUIT" => {
178
+ let resp = self.handle_quit(&mut authenticated_user);
179
+ if let Err(e) = writeln!(stream, "{}", resp) {
180
+ eprintln!("Error sending response: {}", e);
181
+ }
182
+ break;
183
+ }
184
+ "CAPA" => self.handle_capa(),
185
+ _ => "-ERR Unknown command".to_string(),
186
+ };
187
+
188
+ println!("> {}", response.lines().next().unwrap_or(""));
189
+ if let Err(e) = write!(stream, "{}", response) {
190
+ eprintln!("Error sending response: {}", e);
191
+ break;
192
+ }
193
+
194
+ if let Err(e) = stream.flush() {
195
+ eprintln!("Error flushing stream: {}", e);
196
+ break;
197
+ }
198
+ }
199
+ Err(e) => {
200
+ eprintln!("Error reading from client: {}", e);
201
+ break;
202
+ }
203
+ }
204
+ }
205
+
206
+ println!("Client disconnected");
207
+ }
208
+
209
+ fn handle_user(&self, username: &str, pending_user: &mut Option<String>) -> String {
210
+ if username.is_empty() {
211
+ return "-ERR Missing username\r\n".to_string();
212
+ }
213
+
214
+ let users = self.users.lock().unwrap();
215
+ if users.contains_key(username) {
216
+ *pending_user = Some(username.to_string());
217
+ "+OK User accepted\r\n".to_string()
218
+ } else {
219
+ "-ERR User not found\r\n".to_string()
220
+ }
221
+ }
222
+
223
+ fn handle_pass(
224
+ &self,
225
+ password: &str,
226
+ pending_user: &Option<String>,
227
+ authenticated_user: &mut Option<String>,
228
+ ) -> String {
229
+ let username = match pending_user {
230
+ Some(u) => u,
231
+ None => return "-ERR Send USER first\r\n".to_string(),
232
+ };
233
+
234
+ let users = self.users.lock().unwrap();
235
+ if let Some(stored_pass) = users.get(username) {
236
+ if stored_pass == password {
237
+ *authenticated_user = Some(username.clone());
238
+ return "+OK Password accepted\r\n".to_string();
239
+ }
240
+ }
241
+
242
+ "-ERR Invalid password\r\n".to_string()
243
+ }
244
+
245
+ fn handle_stat(&self, authenticated_user: &Option<String>) -> String {
246
+ let username = match authenticated_user {
247
+ Some(u) => u,
248
+ None => return "-ERR Not authenticated\r\n".to_string(),
249
+ };
250
+
251
+ let mailboxes = self.mailboxes.lock().unwrap();
252
+ if let Some(mailbox) = mailboxes.get(username) {
253
+ let (count, size) = mailbox.stat();
254
+ format!("+OK {} {}\r\n", count, size)
255
+ } else {
256
+ "-ERR Mailbox not found\r\n".to_string()
257
+ }
258
+ }
259
+
260
+ fn handle_list(&self, authenticated_user: &Option<String>, args: &str) -> String {
261
+ let username = match authenticated_user {
262
+ Some(u) => u,
263
+ None => return "-ERR Not authenticated\r\n".to_string(),
264
+ };
265
+
266
+ let mailboxes = self.mailboxes.lock().unwrap();
267
+ let mailbox = match mailboxes.get(username) {
268
+ Some(mb) => mb,
269
+ None => return "-ERR Mailbox not found\r\n".to_string(),
270
+ };
271
+
272
+ if args.is_empty() {
273
+ // List all messages
274
+ let (count, size) = mailbox.stat();
275
+ let mut response = format!("+OK {} messages ({} octets)\r\n", count, size);
276
+ for (id, size) in mailbox.list_all() {
277
+ response.push_str(&format!("{} {}\r\n", id, size));
278
+ }
279
+ response.push_str(".\r\n");
280
+ response
281
+ } else {
282
+ // List specific message
283
+ if let Ok(id) = args.parse::<usize>() {
284
+ if let Some(msg) = mailbox.get_message(id) {
285
+ format!("+OK {} {}\r\n", id, msg.size)
286
+ } else {
287
+ "-ERR No such message\r\n".to_string()
288
+ }
289
+ } else {
290
+ "-ERR Invalid message number\r\n".to_string()
291
+ }
292
+ }
293
+ }
294
+
295
+ fn handle_retr(&self, authenticated_user: &Option<String>, args: &str) -> String {
296
+ let username = match authenticated_user {
297
+ Some(u) => u,
298
+ None => return "-ERR Not authenticated\r\n".to_string(),
299
+ };
300
+
301
+ if args.is_empty() {
302
+ return "-ERR Missing message number\r\n".to_string();
303
+ }
304
+
305
+ let id = match args.parse::<usize>() {
306
+ Ok(id) => id,
307
+ Err(_) => return "-ERR Invalid message number\r\n".to_string(),
308
+ };
309
+
310
+ let mailboxes = self.mailboxes.lock().unwrap();
311
+ let mailbox = match mailboxes.get(username) {
312
+ Some(mb) => mb,
313
+ None => return "-ERR Mailbox not found\r\n".to_string(),
314
+ };
315
+
316
+ if let Some(msg) = mailbox.get_message(id) {
317
+ format!("+OK {} octets\r\n{}\r\n.\r\n", msg.size, msg.content)
318
+ } else {
319
+ "-ERR No such message\r\n".to_string()
320
+ }
321
+ }
322
+
323
+ fn handle_dele(&self, authenticated_user: &Option<String>, args: &str) -> String {
324
+ let username = match authenticated_user {
325
+ Some(u) => u,
326
+ None => return "-ERR Not authenticated\r\n".to_string(),
327
+ };
328
+
329
+ if args.is_empty() {
330
+ return "-ERR Missing message number\r\n".to_string();
331
+ }
332
+
333
+ let id = match args.parse::<usize>() {
334
+ Ok(id) => id,
335
+ Err(_) => return "-ERR Invalid message number\r\n".to_string(),
336
+ };
337
+
338
+ let mut mailboxes = self.mailboxes.lock().unwrap();
339
+ let mailbox = match mailboxes.get_mut(username) {
340
+ Some(mb) => mb,
341
+ None => return "-ERR Mailbox not found\r\n".to_string(),
342
+ };
343
+
344
+ if mailbox.delete_message(id) {
345
+ format!("+OK Message {} deleted\r\n", id)
346
+ } else {
347
+ "-ERR No such message\r\n".to_string()
348
+ }
349
+ }
350
+
351
+ fn handle_noop(&self, authenticated_user: &Option<String>) -> String {
352
+ if authenticated_user.is_none() {
353
+ return "-ERR Not authenticated\r\n".to_string();
354
+ }
355
+ "+OK\r\n".to_string()
356
+ }
357
+
358
+ fn handle_rset(&self, authenticated_user: &Option<String>) -> String {
359
+ let username = match authenticated_user {
360
+ Some(u) => u,
361
+ None => return "-ERR Not authenticated\r\n".to_string(),
362
+ };
363
+
364
+ let mut mailboxes = self.mailboxes.lock().unwrap();
365
+ if let Some(mailbox) = mailboxes.get_mut(username) {
366
+ mailbox.reset();
367
+ let (count, size) = mailbox.stat();
368
+ format!("+OK Maildrop has {} messages ({} octets)\r\n", count, size)
369
+ } else {
370
+ "-ERR Mailbox not found\r\n".to_string()
371
+ }
372
+ }
373
+
374
+ fn handle_quit(&self, authenticated_user: &mut Option<String>) -> String {
375
+ if let Some(username) = authenticated_user.take() {
376
+ let mut mailboxes = self.mailboxes.lock().unwrap();
377
+ if let Some(mailbox) = mailboxes.get_mut(&username) {
378
+ mailbox.commit_deletions();
379
+ }
380
+ }
381
+ "+OK POP3 server signing off\r\n".to_string()
382
+ }
383
+
384
+ fn handle_capa(&self) -> String {
385
+ let mut response = "+OK Capability list follows\r\n".to_string();
386
+ response.push_str("USER\r\n");
387
+ response.push_str("UIDL\r\n");
388
+ response.push_str("TOP\r\n");
389
+ response.push_str(".\r\n");
390
+ response
391
+ }
392
+}
393
+
394
+fn main() {
395
+ println!("========================================");
396
+ println!("POP3 Server Example");
397
+ println!("========================================\n");
398
+
399
+ let server = Arc::new(Pop3Server::new());
400
+
401
+ let listener = match TcpListener::bind("127.0.0.1:110") {
402
+ Ok(listener) => {
403
+ println!("POP3 server listening on 127.0.0.1:110");
404
+ println!("\nTest users:");
405
+ println!(" - testuser / testpass (has 3 messages)");
406
+ println!(" - alice / password123 (has 3 messages)");
407
+ println!(" - bob / secret (empty mailbox)");
408
+ println!("\nPress Ctrl+C to stop the server\n");
409
+ listener
410
+ }
411
+ Err(e) => {
412
+ eprintln!("Failed to bind to port 110: {}", e);
413
+ eprintln!("\nNote: Port 110 requires root/administrator privileges.");
414
+ eprintln!("Try running with sudo, or modify the code to use port 1110.");
415
+ return;
416
+ }
417
+ };
418
+
419
+ for stream in listener.incoming() {
420
+ match stream {
421
+ Ok(stream) => {
422
+ let server = Arc::clone(&server);
423
+ thread::spawn(move || {
424
+ server.handle_client(stream);
425
+ });
426
+ }
427
+ Err(e) => {
428
+ eprintln!("Error accepting connection: {}", e);
429
+ }
430
+ }
431
+ }
432
+}