aboutsummaryrefslogtreecommitdiff
path: root/src/smtp.rs
diff options
context:
space:
mode:
authorMroik <mroik@delayed.space>2026-04-09 03:26:58 +0200
committerMroik <mroik@delayed.space>2026-04-13 06:56:11 +0200
commit1254bf15b2dee313a9b1c79be779c6b0f6789f1c (patch)
treec28c775582f2d86a5bdc37d5d9b09023f4b07378 /src/smtp.rs
parent8d55b7c1c3cac3e6cff00be866f1b48a7300268b (diff)
Add SMTP DATA command for mail input
Forwarding and storing are yet to be implemented, but this commit does implement the handling of the DATA command. Signed-off-by: Mroik <mroik@delayed.space>
Diffstat (limited to 'src/smtp.rs')
-rw-r--r--src/smtp.rs58
1 files changed, 55 insertions, 3 deletions
diff --git a/src/smtp.rs b/src/smtp.rs
index 00e2ae8..30cf490 100644
--- a/src/smtp.rs
+++ b/src/smtp.rs
@@ -32,6 +32,7 @@ impl SmtpServer {
client_host: String::new(),
from: None,
to: Vec::new(),
+ data: String::new(),
};
spawn(session.run(stream));
}
@@ -45,6 +46,7 @@ struct SessionHandler {
client_host: String,
from: Option<String>,
to: Vec<String>,
+ data: String,
}
impl SessionHandler {
@@ -60,11 +62,24 @@ impl SessionHandler {
)?;
loop {
+ buffer.clear();
if r.read_line(&mut buffer)? == 0 {
break;
}
log::debug!("Received '{}' from '{}'", buffer.trim(), self.addr);
+ // In this state the server is not expecting commands as the input is part of the email
+ // message
+ if self.state == SessionState::AwaitingMailInput {
+ if buffer.starts_with(".\r\n") {
+ self.process_mail().await?;
+ continue;
+ }
+
+ self.data.push_str(&buffer);
+ continue;
+ }
+
let command = match Command::try_from(buffer.as_str()) {
Err(_) => {
writer.write_all(Reply::InvalidCommand.to_string().as_bytes())?;
@@ -74,8 +89,6 @@ impl SessionHandler {
};
let res = self.apply(command).await?;
writer.write_all(res.to_string().as_bytes())?;
-
- buffer.clear();
}
log::info!("Connection closed by {}", self.addr);
@@ -87,6 +100,7 @@ impl SessionHandler {
Command::HELO(hostname) => self.helo(hostname).await,
Command::MAIL(from) => self.mail(from).await,
Command::RCPT(to) => self.rcpt(to).await,
+ Command::DATA => self.start_data().await,
}
}
@@ -127,12 +141,43 @@ impl SessionHandler {
self.to.push(String::from(to));
Ok(Reply::Completed(String::from("Ok")))
}
+
+ /// Only after having started a mail transaction
+ async fn start_data(&mut self) -> std::result::Result<Reply, anyhow::Error> {
+ if self.state != SessionState::MailTransaction {
+ return Ok(Reply::BadSequence);
+ }
+
+ // Should I return a more meaningful string with this error code?
+ if self.from.is_none() || self.to.is_empty() {
+ return Ok(Reply::InvalidCommand);
+ }
+
+ self.state = SessionState::AwaitingMailInput;
+
+ Ok(Reply::StartMailInput)
+ }
+
+ // TODO: Forward and store email
+ async fn process_mail(&mut self) -> Result<Reply> {
+ if self.from.is_none() || self.to.is_empty() || self.data.trim().is_empty() {
+ return Ok(Reply::InvalidCommand);
+ }
+
+ self.state = SessionState::Normal;
+ self.from = None;
+ self.to.clear();
+ self.data.clear();
+
+ todo!()
+ }
}
#[derive(PartialEq)]
enum SessionState {
WaitingHelo,
MailTransaction,
+ AwaitingMailInput,
Normal,
}
@@ -145,6 +190,7 @@ impl Default for SessionState {
enum Reply {
Ready(String),
Completed(String),
+ StartMailInput,
InvalidCommand,
InvalidParameter,
BadSequence,
@@ -155,9 +201,10 @@ impl ToString for Reply {
match self {
Reply::Ready(hostname) => format!("220 {}", hostname),
Reply::Completed(hostname) => format!("250 {}", hostname),
- Reply::InvalidCommand => format!("500 Command not recognized"),
+ Reply::InvalidCommand => format!("500 Invalid command"),
Reply::InvalidParameter => format!("501 Parameter error"),
Reply::BadSequence => format!("503 Bad sequence of commands"),
+ Reply::StartMailInput => format!("354 <CRLF>.<CRLF>"),
}
}
}
@@ -166,6 +213,7 @@ enum Command {
HELO(String),
MAIL(String),
RCPT(String),
+ DATA,
}
impl TryFrom<&str> for Command {
@@ -190,6 +238,10 @@ impl TryFrom<&str> for Command {
return Ok(Command::RCPT(String::from(to)));
}
+ if value.to_lowercase().starts_with("data") {
+ return Ok(Command::DATA);
+ }
+
Err(anyhow::format_err!("Invalid command"))
}
}
XMR address: 854DmXNrxULU3ZFJVs4Wc8PFhbq29RhqHhY8W6cdWrtFN3qmooKyyeYPcDzZTNRxphhJ5UzASQfAdEMwSteVqymk28aLhqj