aboutsummaryrefslogtreecommitdiff
path: root/src/smtp.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/smtp.rs')
-rw-r--r--src/smtp.rs242
1 files changed, 0 insertions, 242 deletions
diff --git a/src/smtp.rs b/src/smtp.rs
deleted file mode 100644
index 2795ecf..0000000
--- a/src/smtp.rs
+++ /dev/null
@@ -1,242 +0,0 @@
-use std::{
- io::{BufRead, BufReader, Write},
- net::{IpAddr, SocketAddr, TcpListener, TcpStream},
-};
-
-use anyhow::Result;
-use tokio::spawn;
-
-const SERVER_NAME: &str = "";
-
-pub struct SmtpServer {
- listener: TcpListener,
- running: bool,
-}
-
-impl SmtpServer {
- pub fn new(ip: [u8; 4], port: u16) -> Result<Self> {
- Ok(Self {
- listener: TcpListener::bind((IpAddr::from(ip), port))?,
- running: false,
- })
- }
-
- // TODO: trap SIGINT to stop server?
- pub async fn run(&mut self) -> Result<()> {
- self.running = true;
- while self.running {
- let (stream, addr) = self.listener.accept()?;
- let session = SessionHandler {
- addr,
- state: SessionState::default(),
- client_host: String::new(),
- from: None,
- to: Vec::new(),
- data: String::new(),
- };
- spawn(session.run(stream));
- }
- Ok(())
- }
-}
-
-struct SessionHandler {
- addr: SocketAddr,
- state: SessionState,
- client_host: String,
- from: Option<String>,
- to: Vec<String>,
- data: String,
-}
-
-impl SessionHandler {
- async fn run(mut self, stream: TcpStream) -> Result<()> {
- let mut writer = stream.try_clone()?;
- let mut r = BufReader::new(&stream);
- let mut buffer = String::new();
-
- writer.write_all(
- Reply::Ready(String::from(SERVER_NAME))
- .to_string()
- .as_bytes(),
- )?;
-
- 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())?;
- continue;
- }
- Ok(v) => v,
- };
- let res = self.apply(command).await?;
- writer.write_all(res.to_string().as_bytes())?;
- }
-
- log::info!("Connection closed by {}", self.addr);
- Ok(())
- }
-
- 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,
- Command::RCPT(to) => self.rcpt(to).await,
- Command::DATA => self.start_data().await,
- }
- }
-
- /// HELO resets buffers
- async fn helo(&mut self, hostname: &str) -> Result<Reply> {
- self.client_host = String::from(hostname);
- self.state = SessionState::Normal;
- self.from = None;
- self.to.clear();
- 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.to.clear();
- self.state = SessionState::MailTransaction;
-
- Ok(Reply::Completed(String::from("Ok")))
- }
-
- /// TODO: Validate email addresses
- /// Only after having started a mail transaction
- async fn rcpt(&mut self, to: &str) -> Result<Reply> {
- if self.state != SessionState::MailTransaction {
- return Ok(Reply::BadSequence);
- }
-
- if to.is_empty() {
- return Ok(Reply::InvalidParameter);
- }
-
- 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, Default)]
-enum SessionState {
- #[default]
- WaitingHelo,
- MailTransaction,
- AwaitingMailInput,
- Normal,
-}
-
-enum Reply {
- Ready(String),
- Completed(String),
- StartMailInput,
- InvalidCommand,
- InvalidParameter,
- BadSequence,
-}
-
-impl std::fmt::Display for Reply {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- match self {
- Reply::Ready(hostname) => write!(f, "220 {}", hostname),
- Reply::Completed(hostname) => write!(f, "250 {}", hostname),
- Reply::InvalidCommand => write!(f, "500 Invalid command"),
- Reply::InvalidParameter => write!(f, "501 Parameter error"),
- Reply::BadSequence => write!(f, "503 Bad sequence of commands"),
- Reply::StartMailInput => write!(f, "354 <CRLF>.<CRLF>"),
- }
- }
-}
-
-enum Command {
- HELO(String),
- MAIL(String),
- RCPT(String),
- DATA,
-}
-
-impl TryFrom<&str> for Command {
- type Error = anyhow::Error;
-
- fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
- if value.len() >= 5 && value.to_lowercase().starts_with("helo ") {
- 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)));
- }
-
- if value.len() >= 9 && value.to_lowercase().starts_with("rcpt to:<") && value.contains('>')
- {
- let to = &value[9..value.find('>').unwrap()];
- 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