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?;