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