format

Seto committed Oct 9, 2025 at 22:38 UTC c67c1de74f239e459666bf949a627c836e4d7c7e
8 files changed +33 -41
Cargo.toml
+6 -6
@@ -4,13 +4,13 @@ version = "0.1.0"
4 edition = "2021"
5
6 [dependencies]
7 +anyhow = "1"
8 +futures = "0.3"
9 +native-tls = "0.2"
10 +openssl = "0.10"
11 +regex = "1"
12 tokio = { version = "1", features = [ "macros","io-util","net","rt", "rt-multi-thread"] }
8 -tokio-util = { version = "0.7", features = ["codec"] }
13 tokio-native-tls = "0.3"
10 -native-tls = "0.2"
11 -anyhow = "1"
14 +tokio-util = { version = "0.7", features = ["codec"] }
15 tracing = "0.1"
16 tracing-subscriber = "0.3"
14 -regex = "1"
15 -futures = "0.3"
16 -openssl = "0.10"
src/generate_certificate.rs
+1 -1
@@ -3,8 +3,8 @@ use openssl::{
3 pkey::PKey,
4 rsa::Rsa,
5 x509::{
6 - X509, X509NameBuilder,
6 extension::{AuthorityKeyIdentifier, BasicConstraints, SubjectKeyIdentifier},
7 + X509NameBuilder, X509,
8 },
9 };
10
src/handle_session.rs
+18 -21
@@ -1,9 +1,8 @@
1 -use futures::{StreamExt};
1 +use crate::send_commands::send_commands;
2 +use futures::StreamExt;
3 use tokio::io::{AsyncRead, AsyncWrite};
4 use tokio_util::codec::{Framed, LinesCodec};
5
5 -use crate::send_commands::send_commands;
6 -
6 enum SmtpState {
7 Command,
8 Data,
@@ -14,8 +13,8 @@ pub async fn handle_session<S>(stream: S) -> anyhow::Result<()>
13 where
14 S: AsyncRead + AsyncWrite + Unpin,
15 {
17 - let RE_SMTP_MAIL = regex::Regex::new(r"(?i)from: ?<(.+)>").unwrap();
18 - let RE_SMTP_RCPT = regex::Regex::new(r"(?i)to: ?<(.+)>").unwrap();
16 + let re_smtp_mail = regex::Regex::new(r"(?i)from: ?<(.+)>").unwrap();
17 + let re_smtp_rcpt = regex::Regex::new(r"(?i)to: ?<(.+)>").unwrap();
18 let mut message = String::new();
19 let mut state = SmtpState::Command;
20 let mut mailfrom: Option<String> = None;
@@ -34,7 +33,7 @@ where
33 }
34 "MAIL" => {
35 // Handle MAIL FROM command
37 - if let Some(address) = RE_SMTP_MAIL.captures(arg).and_then(|cap| cap.get(1))
36 + if let Some(address) = re_smtp_mail.captures(arg).and_then(|cap| cap.get(1))
37 {
38 mailfrom = Some(address.as_str().to_string());
39 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
@@ -54,19 +53,17 @@ where
53 vec!["503 Error: Send MAIL first".to_string()],
54 )
55 .await?;
56 + } else if let Some(address) =
57 + re_smtp_rcpt.captures(arg).and_then(|cap| cap.get(1))
58 + {
59 + rcpts.push(address.as_str().to_string());
60 + send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
61 } else {
58 - if let Some(address) =
59 - RE_SMTP_RCPT.captures(arg).and_then(|cap| cap.get(1))
60 - {
61 - rcpts.push(address.as_str().to_string());
62 - send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
63 - } else {
64 - send_commands(
65 - &mut framed,
66 - vec!["501 Syntax: RCPT TO: <address>".to_string()],
67 - )
68 - .await?;
69 - }
62 + send_commands(
63 + &mut framed,
64 + vec!["501 Syntax: RCPT TO: <address>".to_string()],
65 + )
66 + .await?;
67 }
68 }
69 "DATA" => {
@@ -103,7 +100,7 @@ where
100 if line.trim() == "." {
101 // The end of the email content has been received
102 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
106 -
103 +
104 // Log the received email
105 tracing::info!(
106 "Email received - From: {:?}, To: {:?}, Size: {} bytes",
@@ -111,7 +108,7 @@ where
108 rcpts,
109 message.len()
110 );
114 -
111 +
112 // reset the state and variables for the next email
113 mailfrom = None;
114 rcpts = Vec::new();
@@ -120,7 +117,7 @@ where
117 } else {
118 // Add the received line to the email content
119 message.push_str(&line);
123 - message.push_str("\n");
120 + message.push('\n');
121 }
122 }
123 SmtpState::Quit => {
src/handle_starttls.rs
+1 -2
@@ -1,9 +1,8 @@
1 +use crate::{generate_certificate::generate_certificate, handle_session::handle_session};
2 use native_tls::{Identity, TlsAcceptor};
3 use tokio::net::TcpStream;
4 use tokio_native_tls::TlsAcceptor as TokioTlsAcceptor;
5
5 -use crate::{generate_certificate::generate_certificate, handle_session::handle_session};
6 -
6 pub async fn handle_starttls(stream: &mut TcpStream) -> anyhow::Result<()> {
7 // ideally the certificate should only be loaded from here and not generated each time
8 let (pem_certificate, pem_private_key) = generate_certificate()?;
src/handle_unsecured_session.rs
+3 -6
@@ -1,13 +1,10 @@
1 +use crate::handle_starttls::handle_starttls;
2 use tokio::{
3 io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter},
4 net::TcpStream,
5 };
6
6 -use crate::handle_starttls::handle_starttls;
7 -
8 -pub async fn handle_unsecured_session(
9 - stream: &mut TcpStream,
10 -) -> anyhow::Result<()> {
7 +pub async fn handle_unsecured_session(stream: &mut TcpStream) -> anyhow::Result<()> {
8 let (reader, writer) = stream.split();
9 let mut reader = BufReader::new(reader);
10 let mut writer = BufWriter::new(writer);
@@ -57,6 +54,6 @@ pub async fn handle_unsecured_session(
54 if is_tls {
55 handle_starttls(stream).await?;
56 }
60 -
57 +
58 Ok(())
59 }
src/listen.rs
+2 -3
@@ -1,9 +1,8 @@
1 +use crate::handle_unsecured_session::handle_unsecured_session;
2 use std::net::SocketAddr;
3 use tokio::io::AsyncWriteExt;
4 use tokio::net::{TcpListener, TcpStream};
5
5 -use crate::handle_unsecured_session::handle_unsecured_session;
6 -
6 pub async fn listen() -> anyhow::Result<()> {
7 let addr = SocketAddr::from(([127, 0, 0, 1], 2525));
8 let listener = TcpListener::bind(addr).await?;
@@ -29,7 +28,7 @@ pub async fn listen() -> anyhow::Result<()> {
28 async fn handle_connection(stream: &mut TcpStream) -> anyhow::Result<()> {
29 // Send greeting
30 stream.write_all(b"220 My SMTP server\r\n").await?;
32 -
31 +
32 // Handle unsecured session which will handle STARTTLS
33 handle_unsecured_session(stream).await
34 }
src/main.rs
+1 -1
@@ -5,6 +5,6 @@ use smbcloud_mail::listen::listen;
5 async fn main() -> Result<()> {
6 // Initialize tracing subscriber for logging
7 tracing_subscriber::fmt::init();
8 -
8 +
9 listen().await
10 }
src/send_commands.rs
+1 -1
@@ -1,4 +1,4 @@
1 -use futures::{SinkExt, StreamExt, stream::iter};
1 +use futures::{stream::iter, SinkExt, StreamExt};
2 use tokio::io::{AsyncRead, AsyncWrite};
3 use tokio_util::codec::{Framed, LinesCodec};
4