Fix UTF-8 decoding issue in STARTTLS implementation
Co-authored-by: setoelkahfi <1797197+setoelkahfi@users.noreply.github.com>
copilot-swe-agent[bot] committed
Oct 9, 2025 at 13:17 UTC
50616ffe1fc132686de1a0386ecae99b411cff7b
4 files changed
+23
-24
src/generate_certificate.rs
+5
-2
@@ -8,7 +8,7 @@ use openssl::{
8
},
9
};
10
11
-pub fn generate_certificate() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
11
+pub fn generate_certificate() -> anyhow::Result<(String, String)> {
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)?;
@@ -40,5 +40,8 @@ pub fn generate_certificate() -> anyhow::Result<(Vec<u8>, Vec<u8>)> {
40
};
41
let (pem_certificate, pem_private_key) = cert_result?;
42
43
- 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
+7
-4
@@ -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();
src/handle_starttls.rs
+4
-13
@@ -2,28 +2,19 @@ use native_tls::{Identity, TlsAcceptor};
2
use tokio::net::TcpStream;
3
use tokio_native_tls::TlsAcceptor as TokioTlsAcceptor;
4
5
-use crate::generate_certificate::generate_certificate;
5
+use crate::{generate_certificate::generate_certificate, handle_session::handle_session};
6
7
pub async fn handle_starttls(stream: 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(tls_stream) => {
16
- // we can now handle the normal SMTP session over TLS
17
- // For now, we'll just create a framed connection to handle encrypted SMTP
18
- use tokio_util::codec::{Framed, LinesCodec};
19
- use futures::StreamExt;
20
-
21
- let mut framed = Framed::new(tls_stream, LinesCodec::new());
22
- while let Some(line_str) = framed.next().await {
23
- let _line = line_str?;
24
- // Basic echo for now - in a real implementation, this would handle SMTP commands
25
- break;
26
- }
16
+ // we can now handle the normal SMTP session
17
+ handle_session(tls_stream).await?;
18
}
19
Err(e) => {
20
tracing::error!("Error establishing SMTP TLS connection: {:?}", e);
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?;