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 +}