aboutsummaryrefslogtreecommitdiff
path: root/src/smtp.rs
diff options
context:
space:
mode:
authorMroik <mroik@delayed.space>2026-04-07 16:38:43 +0200
committerMroik <mroik@delayed.space>2026-04-13 06:56:10 +0200
commit6d5043fc90f327addd587548a061d2d52ce1766e (patch)
treed3ba42ded565a79037a687c37c6040bdd4b9dfb7 /src/smtp.rs
parent1600c4d157e64d7022f9401a53649c30d193cd45 (diff)
Add SMTP start mail transaction
Validation for the sender's email address should be added later on. This is not strictly necessary as the request at this point will already have gone through another MTA, but it should be done just for good measure in case someone decides to expose this software directly to the internet. Signed-off-by: Mroik <mroik@delayed.space>
Diffstat (limited to 'src/smtp.rs')
-rw-r--r--src/smtp.rs34
1 files changed, 31 insertions, 3 deletions
diff --git a/src/smtp.rs b/src/smtp.rs
index 6614897..2ab79f8 100644
--- a/src/smtp.rs
+++ b/src/smtp.rs
@@ -30,6 +30,7 @@ impl SmtpServer {
addr,
state: SessionState::default(),
client_host: String::new(),
+ from: None,
};
spawn(session.run(stream));
}
@@ -41,6 +42,7 @@ struct SessionHandler {
addr: SocketAddr,
state: SessionState,
client_host: String,
+ from: Option<String>,
}
impl SessionHandler {
@@ -81,21 +83,36 @@ impl SessionHandler {
async fn apply(&mut self, command: Command) -> Result<Reply> {
match &command {
Command::HELO(hostname) => self.helo(hostname).await,
+ Command::MAIL(from) => self.mail(from).await,
}
}
/// HELO resets buffers
async fn helo(&mut self, hostname: &str) -> Result<Reply> {
self.client_host = String::from(hostname);
- self.state = SessionState::NextState;
+ self.state = SessionState::Normal;
+ self.from = None;
Ok(Reply::Completed(String::from(SERVER_NAME)))
}
+
+ /// TODO: Validate email address
+ /// MAIL resets buffers
+ async fn mail(&mut self, from: &str) -> Result<Reply> {
+ if from.is_empty() {
+ return Ok(Reply::InvalidParameter);
+ }
+
+ self.from = Some(String::from(from));
+ self.state = SessionState::MailTransaction;
+
+ Ok(Reply::Completed(String::from("Ok")))
+ }
}
enum SessionState {
WaitingHelo,
- /// This is a dev state
- NextState,
+ MailTransaction,
+ Normal,
}
impl Default for SessionState {
@@ -108,6 +125,7 @@ enum Reply {
Ready(String),
Completed(String),
InvalidCommand,
+ InvalidParameter,
}
impl ToString for Reply {
@@ -116,12 +134,14 @@ impl ToString for Reply {
Reply::Ready(hostname) => format!("220 {}", hostname),
Reply::Completed(hostname) => format!("250 {}", hostname),
Reply::InvalidCommand => format!("500 Command not recognized"),
+ Reply::InvalidParameter => format!("501 Parameter error"),
}
}
}
enum Command {
HELO(String),
+ MAIL(String),
}
impl TryFrom<&str> for Command {
@@ -132,6 +152,14 @@ impl TryFrom<&str> for Command {
return Ok(Command::HELO(String::from(&value[5..])));
}
+ if value.len() >= 11
+ && value.to_lowercase().starts_with("mail from:<")
+ && value.contains('>')
+ {
+ let from = &value[11..value.find('>').unwrap()];
+ return Ok(Command::MAIL(String::from(from)));
+ }
+
Err(anyhow::format_err!("Invalid command"))
}
}
XMR address: 854DmXNrxULU3ZFJVs4Wc8PFhbq29RhqHhY8W6cdWrtFN3qmooKyyeYPcDzZTNRxphhJ5UzASQfAdEMwSteVqymk28aLhqj