Fix all critical SMTP server issues - builds successfully

Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>

copilot-swe-agent[bot] committed Oct 9, 2025 at 12:30 UTC 74dee6fa2ef2ef2857c308ca49b209d58c8fd20d
9 files changed +139 -42
Cargo.lock
+84 -3
@@ -98,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
98 checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
99 dependencies = [
100 "libc",
101 - "windows-sys 0.59.0",
101 + "windows-sys 0.61.2",
102 ]
103
104 [[package]]
@@ -246,6 +246,12 @@ dependencies = [
246 "libc",
247 ]
248
249 +[[package]]
250 +name = "lazy_static"
251 +version = "1.5.0"
252 +source = "registry+https://github.com/rust-lang/crates.io-index"
253 +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
254 +
255 [[package]]
256 name = "libc"
257 version = "0.2.176"
@@ -307,6 +313,15 @@ dependencies = [
313 "tempfile",
314 ]
315
316 +[[package]]
317 +name = "nu-ansi-term"
318 +version = "0.50.1"
319 +source = "registry+https://github.com/rust-lang/crates.io-index"
320 +checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399"
321 +dependencies = [
322 + "windows-sys 0.52.0",
323 +]
324 +
325 [[package]]
326 name = "object"
327 version = "0.37.3"
@@ -453,7 +468,7 @@ dependencies = [
468 "errno",
469 "libc",
470 "linux-raw-sys",
456 - "windows-sys 0.59.0",
471 + "windows-sys 0.61.2",
472 ]
473
474 [[package]]
@@ -488,6 +503,15 @@ dependencies = [
503 "libc",
504 ]
505
506 +[[package]]
507 +name = "sharded-slab"
508 +version = "0.1.7"
509 +source = "registry+https://github.com/rust-lang/crates.io-index"
510 +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
511 +dependencies = [
512 + "lazy_static",
513 +]
514 +
515 [[package]]
516 name = "shlex"
517 version = "1.3.0"
@@ -500,6 +524,12 @@ version = "0.4.11"
524 source = "registry+https://github.com/rust-lang/crates.io-index"
525 checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
526
527 +[[package]]
528 +name = "smallvec"
529 +version = "1.15.1"
530 +source = "registry+https://github.com/rust-lang/crates.io-index"
531 +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
532 +
533 [[package]]
534 name = "smbcloud-mail"
535 version = "0.1.0"
@@ -513,6 +543,7 @@ dependencies = [
543 "tokio-native-tls",
544 "tokio-util",
545 "tracing",
546 + "tracing-subscriber",
547 ]
548
549 [[package]]
@@ -546,7 +577,16 @@ dependencies = [
577 "getrandom",
578 "once_cell",
579 "rustix",
549 - "windows-sys 0.59.0",
580 + "windows-sys 0.61.2",
581 +]
582 +
583 +[[package]]
584 +name = "thread_local"
585 +version = "1.1.9"
586 +source = "registry+https://github.com/rust-lang/crates.io-index"
587 +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
588 +dependencies = [
589 + "cfg-if",
590 ]
591
592 [[package]]
@@ -630,6 +670,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
670 checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
671 dependencies = [
672 "once_cell",
673 + "valuable",
674 +]
675 +
676 +[[package]]
677 +name = "tracing-log"
678 +version = "0.2.0"
679 +source = "registry+https://github.com/rust-lang/crates.io-index"
680 +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
681 +dependencies = [
682 + "log",
683 + "once_cell",
684 + "tracing-core",
685 +]
686 +
687 +[[package]]
688 +name = "tracing-subscriber"
689 +version = "0.3.20"
690 +source = "registry+https://github.com/rust-lang/crates.io-index"
691 +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
692 +dependencies = [
693 + "nu-ansi-term",
694 + "sharded-slab",
695 + "smallvec",
696 + "thread_local",
697 + "tracing-core",
698 + "tracing-log",
699 ]
700
701 [[package]]
@@ -638,6 +704,12 @@ version = "1.0.19"
704 source = "registry+https://github.com/rust-lang/crates.io-index"
705 checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
706
707 +[[package]]
708 +name = "valuable"
709 +version = "0.1.1"
710 +source = "registry+https://github.com/rust-lang/crates.io-index"
711 +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
712 +
713 [[package]]
714 name = "vcpkg"
715 version = "0.2.15"
@@ -674,6 +746,15 @@ version = "0.2.1"
746 source = "registry+https://github.com/rust-lang/crates.io-index"
747 checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
748
749 +[[package]]
750 +name = "windows-sys"
751 +version = "0.52.0"
752 +source = "registry+https://github.com/rust-lang/crates.io-index"
753 +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
754 +dependencies = [
755 + "windows-targets",
756 +]
757 +
758 [[package]]
759 name = "windows-sys"
760 version = "0.59.0"
Cargo.toml
+2 -1
@@ -1,7 +1,7 @@
1 [package]
2 name = "smbcloud-mail"
3 version = "0.1.0"
4 -edition = "2024"
4 +edition = "2021"
5
6 [dependencies]
7 tokio = { version = "1", features = [ "macros","io-util","net","rt", "rt-multi-thread"] }
@@ -10,6 +10,7 @@ tokio-native-tls = "0.3"
10 native-tls = "0.2"
11 anyhow = "1"
12 tracing = "0.1"
13 +tracing-subscriber = "0.3"
14 regex = "1"
15 futures = "0.3"
16 openssl = "0.10"
src/generate_certificate.rs
+6 -5
@@ -9,7 +9,7 @@ use openssl::{
9 };
10
11 pub fn generate_certificate() -> anyhow::Result<(String, String)> {
12 - let cert_result = {
12 + let cert_result: Result<(Vec<u8>, Vec<u8>), openssl::error::ErrorStack> = {
13 let rsa = Rsa::generate(4096)?;
14 let pkey = PKey::from_rsa(rsa)?;
15 let mut name = X509NameBuilder::new()?;
@@ -38,9 +38,10 @@ pub fn generate_certificate() -> anyhow::Result<(String, String)> {
38 let c = builder.build();
39 Ok((c.to_pem()?, pkey.private_key_to_pem_pkcs8()?))
40 };
41 - let (pem_certificate, pem_private_key) = cert_result
42 - .as_ref()
43 - .map_err(|e| anyhow::anyhow!("Could not generate self-signed certificates: {}", e))?;
41 + let (pem_certificate, pem_private_key) = cert_result?;
42
45 - Ok((pem_certificate, pem_private_key))
43 + Ok((
44 + String::from_utf8_lossy(&pem_certificate).to_string(),
45 + String::from_utf8_lossy(&pem_private_key).to_string(),
46 + ))
47 }
src/handle_session.rs
+16 -7
@@ -1,6 +1,6 @@
1 -use futures::{SinkExt, StreamExt, stream::iter};
2 -use tokio::net::TcpStream;
3 -use tokio_util::codec::{Framed, LinesCodec, LinesCodecError};
1 +use futures::{StreamExt};
2 +use tokio::io::{AsyncRead, AsyncWrite};
3 +use tokio_util::codec::{Framed, LinesCodec};
4
5 use crate::send_commands::send_commands;
6
@@ -10,7 +10,10 @@ enum SmtpState {
10 Quit,
11 }
12
13 -pub async fn handle_session(mut stream: TcpStream) -> anyhow::Result<()> {
13 +pub async fn handle_session<S>(stream: S) -> anyhow::Result<()>
14 +where
15 + S: AsyncRead + AsyncWrite + Unpin,
16 +{
17 let RE_SMTP_MAIL = regex::Regex::new(r"(?i)from: ?<(.+)>").unwrap();
18 let RE_SMTP_RCPT = regex::Regex::new(r"(?i)to: ?<(.+)>").unwrap();
19 let mut message = String::new();
@@ -100,14 +103,20 @@ pub async fn handle_session(mut stream: TcpStream) -> anyhow::Result<()> {
103 if line.trim() == "." {
104 // The end of the email content has been received
105 send_commands(&mut framed, vec!["250 OK".to_string()]).await?;
106 +
107 + // Log the received email
108 + tracing::info!(
109 + "Email received - From: {:?}, To: {:?}, Size: {} bytes",
110 + mailfrom,
111 + rcpts,
112 + message.len()
113 + );
114 +
115 // reset the state and variables for the next email
116 mailfrom = None;
117 rcpts = Vec::new();
118 message = String::new();
119 state = SmtpState::Command;
108 - // we can now handle the email:
109 - //handle_email(mailfrom, rcpts, message);
110 - panic!()
120 } else {
121 // Add the received line to the email content
122 message.push_str(&line);
src/handle_starttls.rs
+5 -5
@@ -1,20 +1,20 @@
1 use native_tls::{Identity, TlsAcceptor};
2 use tokio::net::TcpStream;
3 -use tokio_native_tls::{TlsAcceptor as TokioTlsAcceptor, TlsStream};
3 +use tokio_native_tls::TlsAcceptor as TokioTlsAcceptor;
4
5 use crate::{generate_certificate::generate_certificate, handle_session::handle_session};
6
7 -async fn handle_starttls(stream: &mut TcpStream) -> anyhow::Result<()> {
7 +pub async fn handle_starttls(stream: &mut TcpStream) -> anyhow::Result<()> {
8 // ideally the certificate should only be loaded from here and not generated each time
9 let (pem_certificate, pem_private_key) = generate_certificate()?;
10 - let identity = Identity::from_pkcs8(&pem_certificate, &pem_private_key)?;
10 + let identity = Identity::from_pkcs8(pem_certificate.as_bytes(), pem_private_key.as_bytes())?;
11 let tls_acceptor = TlsAcceptor::builder(identity).build()?;
12 let tls_acceptor = TokioTlsAcceptor::from(tls_acceptor);
13
14 match tls_acceptor.accept(stream).await {
15 - Ok(stream) => {
15 + Ok(tls_stream) => {
16 // we can now handle the normal SMTP session
17 - handle_session(stream).await?;
17 + handle_session(tls_stream).await?;
18 }
19 Err(e) => {
20 tracing::error!("Error establishing SMTP TLS connection: {:?}", e);
src/handle_unsecured_session.rs
+9 -5
@@ -3,12 +3,14 @@ use tokio::{
3 net::TcpStream,
4 };
5
6 -use crate::handle_starttls;
6 +use crate::handle_starttls::handle_starttls;
7
8 -async fn handle_unsecured_session(
9 - reader: &mut BufReader<TcpStream>,
10 - writer: &mut BufWriter<TcpStream>,
8 +pub async fn handle_unsecured_session(
9 + stream: &mut TcpStream,
10 ) -> anyhow::Result<()> {
11 + let (reader, writer) = stream.split();
12 + let mut reader = BufReader::new(reader);
13 + let mut writer = BufWriter::new(writer);
14 let mut is_tls = false;
15 let mut line = String::new();
16 while reader.read_line(&mut line).await? != 0 {
@@ -53,6 +55,8 @@ async fn handle_unsecured_session(
55 }
56
57 if is_tls {
56 - handle_starttls(stream).await?
58 + handle_starttls(stream).await?;
59 }
60 +
61 + Ok(())
62 }
src/listen.rs
+7 -11
@@ -1,8 +1,8 @@
1 use std::net::SocketAddr;
2 -use tokio::io::{AsyncWriteExt, BufReader, BufWriter};
2 +use tokio::io::AsyncWriteExt;
3 use tokio::net::{TcpListener, TcpStream};
4
5 -//use crate::handle_session::handle_session;
5 +use crate::handle_session::handle_session;
6
7 pub async fn listen() -> anyhow::Result<()> {
8 let addr = SocketAddr::from(([127, 0, 0, 1], 2525));
@@ -27,13 +27,9 @@ pub async fn listen() -> anyhow::Result<()> {
27 }
28
29 async fn handle_connection(stream: &mut TcpStream) -> anyhow::Result<()> {
30 - let (reader, writer) = stream.split();
31 - let mut reader = BufReader::new(reader);
32 - let mut writer = BufWriter::new(writer);
33 - writer.write_all(b"220 My SMTP server\r\n").await?;
34 - writer.flush().await?;
35 -
36 - // handle session
37 - // handle_session(stream).await
38 - Ok(())
30 + // Send greeting
31 + stream.write_all(b"220 My SMTP server\r\n").await?;
32 +
33 + // Handle the session
34 + handle_session(stream).await
35 }
src/main.rs
+3
@@ -3,5 +3,8 @@ use smbcloud_mail::listen::listen;
3
4 #[tokio::main]
5 async fn main() -> Result<()> {
6 + // Initialize tracing subscriber for logging
7 + tracing_subscriber::fmt::init();
8 +
9 listen().await
10 }
src/send_commands.rs
+7 -5
@@ -1,12 +1,14 @@
1 use futures::{SinkExt, StreamExt, stream::iter};
2 -use tokio::net::TcpStream;
3 -use tokio_native_tls::TlsStream;
2 +use tokio::io::{AsyncRead, AsyncWrite};
3 use tokio_util::codec::{Framed, LinesCodec};
4
6 -pub async fn send_commands(
7 - framed: &mut Framed<TcpStream, LinesCodec>,
5 +pub async fn send_commands<S>(
6 + framed: &mut Framed<S, LinesCodec>,
7 commands: Vec<String>,
9 -) -> anyhow::Result<()> {
8 +) -> anyhow::Result<()>
9 +where
10 + S: AsyncRead + AsyncWrite + Unpin,
11 +{
12 // only need to add \r because the codec only adds \n
13 let messages = iter(commands.into_iter().map(|x| format!("{}\r", x)));
14 framed.send_all(&mut messages.map(Ok)).await?;