1 use crate::pop3_storage::Pop3Mailbox;
2 use crate::send_commands::send_commands;
3 use futures::StreamExt;
4 use tokio::io::{AsyncRead, AsyncWrite};
5 use tokio_util::codec::{Framed, LinesCodec};
6
7 #[derive(Debug, Clone, PartialEq)]
8 enum Pop3State {
9 Authorization,
10 Transaction,
11 Update,
12 }
13
14 pub async fn handle_pop3_session<S>(stream: S, mailbox: Pop3Mailbox) -> anyhow::Result<()>
15 where
16 S: AsyncRead + AsyncWrite + Unpin,
17 {
18 let mut state = Pop3State::Authorization;
19 let mut username: Option<String> = None;
20 let mut authenticated_user: Option<String> = None;
21
22 let mut framed = Framed::new(stream, LinesCodec::new());
23
24 // Send greeting
25 send_commands(&mut framed, vec!["+OK POP3 server ready".to_string()]).await?;
26
27 while let Some(line_str) = framed.next().await {
28 let line = line_str?;
29 let parts: Vec<&str> = line.trim().split_whitespace().collect();
30
31 if parts.is_empty() {
32 send_commands(&mut framed, vec!["-ERR Invalid command".to_string()]).await?;
33 continue;
34 }
35
36 let command = parts[0].to_uppercase();
37
38 match state {
39 Pop3State::Authorization => {
40 match command.as_str() {
41 "USER" => {
42 if parts.len() < 2 {
43 send_commands(&mut framed, vec!["-ERR Username required".to_string()]).await?;
44 } else {
45 username = Some(parts[1].to_string());
46 send_commands(&mut framed, vec![format!("+OK User {} accepted", parts[1])]).await?;
47 }
48 }
49 "PASS" => {
50 if username.is_none() {
51 send_commands(&mut framed, vec!["-ERR No username given".to_string()]).await?;
52 } else if parts.len() < 2 {
53 send_commands(&mut framed, vec!["-ERR Password required".to_string()]).await?;
54 } else {
55 // SECURITY NOTE: For demonstration purposes only - accepts any password
56 // In production, implement proper authentication against a user database
57 // with securely hashed passwords
58 let user = username.clone().unwrap();
59 authenticated_user = Some(user.clone());
60
61 let (count, size) = mailbox.get_stats(&user);
62 send_commands(&mut framed, vec![format!("+OK {} messages ({} octets)", count, size)]).await?;
63 state = Pop3State::Transaction;
64 }
65 }
66 "CAPA" => {
67 send_commands(&mut framed, vec![
68 "+OK Capability list follows".to_string(),
69 "USER".to_string(),
70 ".".to_string(),
71 ]).await?;
72 }
73 "QUIT" => {
74 send_commands(&mut framed, vec!["+OK Goodbye".to_string()]).await?;
75 break;
76 }
77 _ => {
78 send_commands(&mut framed, vec!["-ERR Unknown command".to_string()]).await?;
79 }
80 }
81 }
82 Pop3State::Transaction => {
83 match command.as_str() {
84 "STAT" => {
85 if let Some(ref user) = authenticated_user {
86 let (count, size) = mailbox.get_stats(user);
87 send_commands(&mut framed, vec![format!("+OK {} {}", count, size)]).await?;
88 } else {
89 send_commands(&mut framed, vec!["-ERR Not authenticated".to_string()]).await?;
90 }
91 }
92 "LIST" => {
93 if let Some(ref user) = authenticated_user {
94 let messages = mailbox.get_messages(user);
95 let non_deleted: Vec<_> = messages.iter().filter(|m| !m.deleted).collect();
96
97 if parts.len() == 1 {
98 // List all messages
99 let (count, size) = mailbox.get_stats(user);
100 let mut response = vec![format!("+OK {} messages ({} octets)", count, size)];
101 for msg in non_deleted {
102 response.push(format!("{} {}", msg.id, msg.size));
103 }
104 response.push(".".to_string());
105 send_commands(&mut framed, response).await?;
106 } else if let Ok(msg_id) = parts[1].parse::<usize>() {
107 // List specific message
108 if let Some(msg) = non_deleted.iter().find(|m| m.id == msg_id) {
109 send_commands(&mut framed, vec![format!("+OK {} {}", msg.id, msg.size)]).await?;
110 } else {
111 send_commands(&mut framed, vec!["-ERR No such message".to_string()]).await?;
112 }
113 } else {
114 send_commands(&mut framed, vec!["-ERR Invalid message number".to_string()]).await?;
115 }
116 } else {
117 send_commands(&mut framed, vec!["-ERR Not authenticated".to_string()]).await?;
118 }
119 }
120 "RETR" => {
121 if let Some(ref user) = authenticated_user {
122 if parts.len() < 2 {
123 send_commands(&mut framed, vec!["-ERR Message number required".to_string()]).await?;
124 } else if let Ok(msg_id) = parts[1].parse::<usize>() {
125 let messages = mailbox.get_messages(user);
126 if let Some(msg) = messages.iter().find(|m| m.id == msg_id && !m.deleted) {
127 let mut response = vec![format!("+OK {} octets", msg.size)];
128 response.extend(msg.content.lines().map(|l| l.to_string()));
129 response.push(".".to_string());
130 send_commands(&mut framed, response).await?;
131 } else {
132 send_commands(&mut framed, vec!["-ERR No such message".to_string()]).await?;
133 }
134 } else {
135 send_commands(&mut framed, vec!["-ERR Invalid message number".to_string()]).await?;
136 }
137 } else {
138 send_commands(&mut framed, vec!["-ERR Not authenticated".to_string()]).await?;
139 }
140 }
141 "DELE" => {
142 if let Some(ref user) = authenticated_user {
143 if parts.len() < 2 {
144 send_commands(&mut framed, vec!["-ERR Message number required".to_string()]).await?;
145 } else if let Ok(msg_id) = parts[1].parse::<usize>() {
146 if mailbox.mark_deleted(user, msg_id) {
147 send_commands(&mut framed, vec![format!("+OK Message {} deleted", msg_id)]).await?;
148 } else {
149 send_commands(&mut framed, vec!["-ERR No such message".to_string()]).await?;
150 }
151 } else {
152 send_commands(&mut framed, vec!["-ERR Invalid message number".to_string()]).await?;
153 }
154 } else {
155 send_commands(&mut framed, vec!["-ERR Not authenticated".to_string()]).await?;
156 }
157 }
158 "NOOP" => {
159 send_commands(&mut framed, vec!["+OK".to_string()]).await?;
160 }
161 "RSET" => {
162 if let Some(ref user) = authenticated_user {
163 mailbox.reset_deleted(user);
164 let (count, size) = mailbox.get_stats(user);
165 send_commands(&mut framed, vec![format!("+OK {} messages ({} octets)", count, size)]).await?;
166 } else {
167 send_commands(&mut framed, vec!["-ERR Not authenticated".to_string()]).await?;
168 }
169 }
170 "QUIT" => {
171 state = Pop3State::Update;
172 break;
173 }
174 "CAPA" => {
175 send_commands(&mut framed, vec![
176 "+OK Capability list follows".to_string(),
177 "USER".to_string(),
178 ".".to_string(),
179 ]).await?;
180 }
181 _ => {
182 send_commands(&mut framed, vec!["-ERR Unknown command".to_string()]).await?;
183 }
184 }
185 }
186 Pop3State::Update => {
187 break;
188 }
189 }
190 }
191
192 // Update state - delete marked messages
193 if state == Pop3State::Update {
194 if let Some(ref user) = authenticated_user {
195 mailbox.delete_marked(user);
196 tracing::info!("POP3 session ended for user: {}", user);
197 }
198 send_commands(&mut framed, vec!["+OK Goodbye".to_string()]).await?;
199 }
200
201 Ok(())
202 }