use crate::config::ImapConfig;
use crate::error::{Error, Result};
use async_imap::Session;
use rustls::pki_types::ServerName;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio_rustls::TlsConnector;
use tokio_util::compat::{Compat, TokioAsyncReadCompatExt};
use tracing::{debug, info};
pub type ImapSession = Session<Compat<tokio_rustls::client::TlsStream<TcpStream>>>;
fn tls_connector() -> TlsConnector {
let config = rustls::ClientConfig::builder()
.dangerous()
.with_custom_certificate_verifier(Arc::new(DangerousVerifier))
.with_no_client_auth();
TlsConnector::from(Arc::new(config))
}
pub async fn connect(config: &ImapConfig) -> Result<ImapSession> {
let addr = format!("{}:{}", config.host, config.port);
debug!("Connecting to IMAP server at {}", addr);
let tcp_stream = TcpStream::connect(&addr).await?;
let mut client = async_imap::Client::new(tcp_stream.compat());
client
.run_command_and_check_ok("STARTTLS", None)
.await
.map_err(|e| Error::Tls(format!("STARTTLS failed: {e}")))?;
let connector = tls_connector();
let server_name = ServerName::try_from(config.host.clone())
.map_err(|e| Error::Tls(format!("Invalid server name: {e}")))?;
let inner = client.into_inner().into_inner();
let tls_stream = connector
.connect(server_name, inner)
.await
.map_err(|e| Error::Tls(e.to_string()))?;
let tls_client = async_imap::Client::new(tls_stream.compat());
let session = tls_client
.login(&config.username, &config.password)
.await
.map_err(|(e, _)| Error::Imap(format!("Login failed: {e}")))?;
info!("Connected to IMAP server");
Ok(session)
}
pub async fn select(session: &mut ImapSession, folder: &str) -> Result<()> {
session
.select(folder)
.await
.map_err(|e| Error::Imap(format!("Failed to select {folder}: {e}")))?;
Ok(())
}
#[derive(Debug)]
struct DangerousVerifier;
impl rustls::client::danger::ServerCertVerifier for DangerousVerifier {
fn verify_server_cert(
&self,
_end_entity: &rustls::pki_types::CertificateDer<'_>,
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
_server_name: &rustls::pki_types::ServerName<'_>,
_ocsp_response: &[u8],
_now: rustls::pki_types::UnixTime,
) -> std::result::Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
Ok(rustls::client::danger::ServerCertVerified::assertion())
}
fn verify_tls12_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn verify_tls13_signature(
&self,
_message: &[u8],
_cert: &rustls::pki_types::CertificateDer<'_>,
_dss: &rustls::DigitallySignedStruct,
) -> std::result::Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
}
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
vec![
rustls::SignatureScheme::RSA_PKCS1_SHA256,
rustls::SignatureScheme::RSA_PKCS1_SHA384,
rustls::SignatureScheme::RSA_PKCS1_SHA512,
rustls::SignatureScheme::ECDSA_NISTP256_SHA256,
rustls::SignatureScheme::ECDSA_NISTP384_SHA384,
rustls::SignatureScheme::ECDSA_NISTP521_SHA512,
rustls::SignatureScheme::RSA_PSS_SHA256,
rustls::SignatureScheme::RSA_PSS_SHA384,
rustls::SignatureScheme::RSA_PSS_SHA512,
rustls::SignatureScheme::ED25519,
]
}
}