use super::protocols::{StarttlsNegotiator, StarttlsProtocol};
use crate::Result;
use async_trait::async_trait;
use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWriteExt, BufReader};
pub struct ImapNegotiator;
impl ImapNegotiator {
pub fn new() -> Self {
Self
}
async fn read_response<S>(reader: &mut BufReader<&mut S>) -> Result<String>
where
S: AsyncRead + Unpin,
{
let mut line = String::new();
reader.read_line(&mut line).await?;
Ok(line)
}
}
#[async_trait]
impl StarttlsNegotiator for ImapNegotiator {
async fn negotiate_starttls(&self, stream: &mut tokio::net::TcpStream) -> Result<()> {
let mut reader = BufReader::new(stream);
let greeting = Self::read_response(&mut reader).await?;
if !greeting.starts_with("* OK") {
return Err(crate::error::TlsError::StarttlsError {
protocol: "IMAP".to_string(),
details: format!("Greeting failed: {}", greeting),
});
}
reader.get_mut().write_all(b"a001 CAPABILITY\r\n").await?;
reader.get_mut().flush().await?;
let mut starttls_supported = false;
loop {
let line = Self::read_response(&mut reader).await?;
if line.to_uppercase().contains("STARTTLS") {
starttls_supported = true;
}
if line.starts_with("a001 OK") {
break;
}
if line.starts_with("a001 NO") || line.starts_with("a001 BAD") {
return Err(crate::error::TlsError::StarttlsError {
protocol: "IMAP".to_string(),
details: "CAPABILITY command failed".to_string(),
});
}
}
if !starttls_supported {
return Err(crate::error::TlsError::StarttlsError {
protocol: "IMAP".to_string(),
details: "Server does not support STARTTLS".to_string(),
});
}
reader.get_mut().write_all(b"a002 STARTTLS\r\n").await?;
reader.get_mut().flush().await?;
let response = Self::read_response(&mut reader).await?;
if !response.starts_with("a002 OK") {
return Err(crate::error::TlsError::StarttlsError {
protocol: "IMAP".to_string(),
details: format!("STARTTLS failed: {}", response),
});
}
Ok(())
}
fn protocol(&self) -> StarttlsProtocol {
StarttlsProtocol::IMAP
}
fn expected_greeting(&self) -> Option<&str> {
Some("* OK")
}
}
impl Default for ImapNegotiator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_imap_negotiator_creation() {
let negotiator = ImapNegotiator::new();
assert_eq!(negotiator.protocol(), StarttlsProtocol::IMAP);
assert_eq!(negotiator.expected_greeting(), Some("* OK"));
}
}