use crate::error::{ProtocolError, SmtpError, SmtpOp};
use crate::protocol::{
format_command,
ehlo_advertises_starttls,
};
use crate::session::SessionState;
use crate::tracing_helpers::{smtp_debug, smtp_error};
use crate::transport::StartTlsCapable;
use super::SmtpClient;
impl<T: StartTlsCapable> SmtpClient<T> {
pub async fn connect_starttls(transport: T, ehlo_domain: &str) -> Result<Self, SmtpError> {
let mut client = Self::connect(transport, ehlo_domain).await?;
client.starttls().await?;
Ok(client)
}
pub async fn starttls(&mut self) -> Result<(), SmtpError> {
self.assert_state_in(&[SessionState::Authentication])?;
smtp_debug!("STARTTLS: requesting upgrade");
if !ehlo_advertises_starttls(&self.capabilities) {
smtp_error!("STARTTLS: extension not advertised; refusing to fall back to plaintext");
self.mark_closed_on_logical_failure();
return Err(ProtocolError::ExtensionUnavailable { name: "STARTTLS" }.into());
}
self.transition(SessionState::StartTls)?;
self.write_all(&format_command("STARTTLS")).await?;
self.expect_code(220, SmtpOp::StartTls).await?;
let residue = self.rx_buf.len() - self.rx_pos;
if residue > 0 {
smtp_error!(
byte_count = residue,
"STARTTLS: refusing to upgrade due to non-empty rx buffer (injection defense)"
);
self.mark_closed_on_logical_failure();
return Err(ProtocolError::StartTlsBufferResidue {
byte_count: residue,
}
.into());
}
self.capabilities.clear();
self.transport.upgrade_to_tls().await.map_err(|e| {
smtp_error!("STARTTLS: TLS upgrade failed at transport layer");
self.mark_closed_on_logical_failure();
SmtpError::Io(e)
})?;
self.audit.on_event(&crate::audit::SmtpAuditEvent::TlsUpgraded);
self.transition(SessionState::Ehlo)?;
let domain = self.ehlo_domain.clone();
self.send_ehlo(&domain).await?;
smtp_debug!(
capability_count = self.capabilities.len(),
"STARTTLS: upgrade complete; re-EHLO done"
);
Ok(())
}
}