From a3f581433fb8a26d4cd509a886c1e0ecbb94b066 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaury1093@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:30:23 +0100 Subject: [PATCH] feat(core): Update async-smtp to 0.9 --- Cargo.lock | 49 ++--------- backend/Cargo.toml | 2 +- core/Cargo.toml | 9 +- core/src/rules.rs | 18 ++++ core/src/smtp/connect.rs | 156 +++++++++++----------------------- core/src/smtp/error.rs | 54 ++++++++---- core/src/smtp/gmail.rs | 2 +- core/src/smtp/mod.rs | 17 ++-- core/src/smtp/parser.rs | 19 +++-- core/src/syntax/mod.rs | 2 +- core/src/util/input_output.rs | 118 +++++++++++++++---------- core/src/util/sentry.rs | 4 +- 12 files changed, 210 insertions(+), 240 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf494cba5..dcfcba65a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,18 +335,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-native-tls" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe" -dependencies = [ - "native-tls", - "thiserror", - "tokio", - "url", -] - [[package]] name = "async-reactor-trait" version = "1.1.0" @@ -372,24 +360,17 @@ dependencies = [ [[package]] name = "async-smtp" -version = "0.6.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ade89127f9e0d44f9e83cf574d499060005cd45b7dc76be89c0167487fe8edd" +checksum = "00d1f1a16e5abad3ada9f1f23dbc2f354b138121b90533381be62dada6cbf40a" dependencies = [ - "async-native-tls", - "async-trait", + "anyhow", "base64 0.13.1", - "bufstream", - "fast-socks5 0.8.2", "futures", "hostname", "log", "nom", "pin-project", - "pin-utils", - "serde", - "serde_derive", - "serde_json", "thiserror", "tokio", ] @@ -528,12 +509,6 @@ dependencies = [ "piper", ] -[[package]] -name = "bufstream" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" - [[package]] name = "bumpalo" version = "3.16.0" @@ -577,14 +552,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "check-if-email-exists" version = "0.10.0" dependencies = [ - "async-native-tls", + "anyhow", "async-recursion", "async-smtp", "chrono", "config", "derive_builder 0.20.2", "fantoccini", - "fast-socks5 0.9.6", + "fast-socks5", "futures", "hickory-proto", "hickory-resolver", @@ -602,6 +577,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tokio-socks", "tracing", ] @@ -1274,19 +1250,6 @@ dependencies = [ "webdriver", ] -[[package]] -name = "fast-socks5" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961ce1761191c157145a8c9f0c3ceabecd3a729d65c9a8d443674eaee3420f7e" -dependencies = [ - "anyhow", - "log", - "thiserror", - "tokio", - "tokio-stream", -] - [[package]] name = "fast-socks5" version = "0.9.6" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ff1d51dc9..7ce3794a0 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -7,7 +7,7 @@ publish = false [dependencies] anyhow = "1.0" -async-smtp = "0.6" +async-smtp = { version = "0.9.1", features = ["runtime-tokio"] } check-if-email-exists = { path = "../core", features = ["sentry"] } config = "0.14" csv = "1.3.0" diff --git a/core/Cargo.toml b/core/Cargo.toml index 5f63915e7..2246bfbab 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,9 +13,9 @@ readme = "../README.md" repository = "https://github.com/reacherhq/check-if-email-exists" [dependencies] -async-native-tls = { version = "0.4", default-features = false } +anyhow = "1.0" async-recursion = "1.0.5" -async-smtp = { version = "0.6.0", features = ["socks5"] } +async-smtp = { version = "0.9.1", features = ["runtime-tokio"] } chrono = { version = "0.4.31", features = ["serde"] } config = "0.14" derive_builder = "0.20" @@ -37,7 +37,6 @@ sentry = { version = "0.23", optional = true } serde = { version = "1.0.214", features = ["derive"] } serde_json = "1.0.133" thiserror = "1.0" -tracing = "0.1.40" - -[dev-dependencies] tokio = { version = "1.40.0" } +tokio-socks = "0.5" +tracing = "0.1.40" diff --git a/core/src/rules.rs b/core/src/rules.rs index f578cdabc..30c209126 100644 --- a/core/src/rules.rs +++ b/core/src/rules.rs @@ -85,3 +85,21 @@ pub fn has_rule(domain: &str, host: &str, rule: &Rule) -> bool { || does_mx_have_rule(host, rule) || does_mx_suffix_have_rule(host, rule) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_skip_catch_all() { + assert_eq!( + true, + has_rule("gmail.com", "alt4.aspmx.l.google.com.", &Rule::SkipCatchAll) + ); + + assert_eq!( + true, + has_rule("domain.com", ".antispamcloud.com.", &Rule::SkipCatchAll) + ) + } +} diff --git a/core/src/smtp/connect.rs b/core/src/smtp/connect.rs index a7c85000c..5d21ff67a 100644 --- a/core/src/smtp/connect.rs +++ b/core/src/smtp/connect.rs @@ -14,25 +14,29 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use async_native_tls::TlsConnector; use async_recursion::async_recursion; -use async_smtp::{ - smtp::{commands::*, extension::ClientId, ServerAddress, Socks5Config}, - ClientTlsParameters, EmailAddress, SmtpClient, SmtpTransport, -}; +use async_smtp::commands::{MailCommand, RcptCommand}; +use async_smtp::extension::ClientId; +use async_smtp::{SmtpClient, SmtpTransport}; use rand::rngs::SmallRng; use rand::{distributions::Alphanumeric, Rng, SeedableRng}; use std::iter; use std::str::FromStr; -use std::time::Duration; +use tokio::io::{AsyncBufRead, AsyncRead, AsyncWrite, BufStream}; +use tokio::net::TcpStream; +use tokio_socks::tcp::Socks5Stream; use super::parser; use super::{SmtpDetails, SmtpError}; -use crate::LOG_TARGET; use crate::{ rules::{has_rule, Rule}, util::input_output::CheckEmailInput, }; +use crate::{EmailAddress, LOG_TARGET}; + +// Define a new trait that combines AsyncRead, AsyncWrite, and Unpin +trait AsyncReadWrite: AsyncRead + AsyncWrite + Unpin + Send {} +impl AsyncReadWrite for T {} /// Try to send an smtp command, close and return Err if fails. macro_rules! try_smtp ( @@ -40,85 +44,46 @@ macro_rules! try_smtp ( if let Err(err) = $res { log::debug!(target: LOG_TARGET, "[email={}] Closing [host={}:{}], because of error '{:?}'.", $to_email, $host, $port, err); // Try to close the connection, but ignore if there's an error. - let _ = $client.close().await; + let _ = $client.quit().await; - return Err(SmtpError::SmtpError(err)); + return Err(SmtpError::AsyncSmtpError(err)); } }) ); /// Attempt to connect to host via SMTP, and return SMTP client on success. async fn connect_to_host( - domain: &str, host: &str, port: u16, input: &CheckEmailInput, -) -> Result { - let smtp_timeout = if let Some(t) = input.smtp_timeout { - if has_rule(domain, host, &Rule::SmtpTimeout45s) { - let duration = t.max(Duration::from_secs(45)); - log::debug!( - target: LOG_TARGET, - "[email={}] Bumping SMTP timeout to {duration:?} because of rule", - input.to_email, - ); - Some(duration) - } else { - input.smtp_timeout - } - } else { - None - }; - +) -> Result>>, SmtpError> { // hostname verification fails if it ends with '.', for example, using // SOCKS5 proxies we can `io: incomplete` error. let host = host.trim_end_matches('.').to_string(); - let security = { - let tls_params: ClientTlsParameters = ClientTlsParameters::new( - host.clone(), - TlsConnector::new() - .use_sni(true) - .danger_accept_invalid_certs(true) - .danger_accept_invalid_hostnames(true), - ); - - input.smtp_security.to_client_security(tls_params) - }; - - let mut smtp_client = SmtpClient::with_security( - ServerAddress { - host: host.clone(), - port, - }, - security, - ) - .hello_name(ClientId::Domain(input.hello_name.clone())) - .timeout(smtp_timeout); - - if let Some(proxy) = &input.proxy { - let socks5_config = match (&proxy.username, &proxy.password) { - (Some(username), Some(password)) => Socks5Config::new_with_user_pass( - proxy.host.clone(), - proxy.port, - username.clone(), - password.clone(), - ), - _ => Socks5Config::new(proxy.host.clone(), proxy.port), + let smtp_client = SmtpClient::new().hello_name(ClientId::Domain(input.hello_name.clone())); + + let stream: BufStream> = if let Some(proxy) = &input.proxy { + let target_addr = format!("{}:{}", host, port); + let socks_stream = match (&proxy.username, &proxy.password) { + (Some(username), Some(password)) => { + Socks5Stream::connect_with_password( + (proxy.host.as_ref(), proxy.port), + target_addr, + username, + password, + ) + .await? + } + _ => Socks5Stream::connect((proxy.host.as_ref(), proxy.port), target_addr).await?, }; + BufStream::new(Box::new(socks_stream) as Box) + } else { + let tcp_stream = TcpStream::connect(format!("{}:{}", host, port)).await?; + BufStream::new(Box::new(tcp_stream) as Box) + }; - smtp_client = smtp_client.use_socks5(socks5_config); - } - - let mut smtp_transport = smtp_client.into_transport(); - - try_smtp!( - smtp_transport.connect().await, - smtp_transport, - input.to_email, - host, - port - ); + let mut smtp_transport = SmtpTransport::new(smtp_client, stream).await?; // "MAIL FROM: user@example.org" let from_email = EmailAddress::from_str(input.from_email.as_ref()).unwrap_or_else(|_| { @@ -130,7 +95,8 @@ async fn connect_to_host( }); try_smtp!( smtp_transport - .command(MailCommand::new(Some(from_email), vec![],)) + .get_mut() + .command(MailCommand::new(Some(from_email.into_inner()), vec![],)) .await, smtp_transport, input.to_email, @@ -154,14 +120,14 @@ struct Deliverability { /// Check if `to_email` exists on host SMTP server. This is the core logic of /// this tool. -async fn email_deliverable( - smtp_transport: &mut SmtpTransport, +async fn email_deliverable( + smtp_transport: &mut SmtpTransport, to_email: &EmailAddress, ) -> Result { // "RCPT TO: " - // FIXME Do not clone `to_email`? match smtp_transport - .command(RcptCommand::new(to_email.clone(), vec![])) + .get_mut() + .command(RcptCommand::new(to_email.clone().into_inner(), vec![])) .await { Ok(_) => { @@ -228,14 +194,14 @@ async fn email_deliverable( } // Return all unparsable errors,. - Err(SmtpError::SmtpError(err)) + Err(SmtpError::AsyncSmtpError(err)) } } } /// Verify the existence of a catch-all on the domain. -async fn smtp_is_catch_all( - smtp_transport: &mut SmtpTransport, +async fn smtp_is_catch_all( + smtp_transport: &mut SmtpTransport, domain: &str, host: &str, input: &CheckEmailInput, @@ -276,7 +242,7 @@ async fn create_smtp_future( ) -> Result<(bool, Deliverability), SmtpError> { // FIXME If the SMTP is not connectable, we should actually return an // Ok(SmtpDetails { can_connect_smtp: false, ... }). - let mut smtp_transport = connect_to_host(domain, host, port, input).await?; + let mut smtp_transport = connect_to_host(host, port, input).await?; let is_catch_all = smtp_is_catch_all(&mut smtp_transport, domain, host, input) .await @@ -304,8 +270,8 @@ async fn create_smtp_future( input.to_email ); - let _ = smtp_transport.close().await; - smtp_transport = connect_to_host(domain, host, port, input).await?; + let _ = smtp_transport.quit().await; + smtp_transport = connect_to_host(host, port, input).await?; result = email_deliverable(&mut smtp_transport, to_email).await; } } @@ -313,7 +279,10 @@ async fn create_smtp_future( result? }; - smtp_transport.close().await.map_err(SmtpError::SmtpError)?; + smtp_transport + .quit() + .await + .map_err(SmtpError::AsyncSmtpError)?; Ok((is_catch_all, deliverability)) } @@ -394,26 +363,3 @@ pub async fn check_smtp_with_retry( _ => result, } } - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn should_skip_catch_all() { - let smtp_client = SmtpClient::new("gmail.com".into()); - let mut smtp_transport = smtp_client.into_transport(); - - let r = smtp_is_catch_all( - &mut smtp_transport, - "gmail.com", - "alt4.aspmx.l.google.com.", - &CheckEmailInput::default(), - ) - .await; - - assert!(!smtp_transport.is_connected()); // We shouldn't connect to google servers. - assert!(r.is_ok()); - assert!(!r.unwrap()) - } -} diff --git a/core/src/smtp/error.rs b/core/src/smtp/error.rs index 27e1bab63..6e6b8d264 100644 --- a/core/src/smtp/error.rs +++ b/core/src/smtp/error.rs @@ -15,29 +15,20 @@ // along with this program. If not, see . use super::gmail::GmailError; - use super::headless::HeadlessError; use super::outlook::microsoft365::Microsoft365Error; use super::parser; use super::yahoo::YahooError; use crate::util::ser_with_display::ser_with_display; -use async_smtp::smtp::error::Error as AsyncSmtpError; -use fast_socks5::SocksError; +use async_smtp::error::Error as AsyncSmtpError; use serde::Serialize; +use std::time::Duration; use thiserror::Error; /// Error occured connecting to this email server via SMTP. #[derive(Debug, Error, Serialize)] #[serde(tag = "type", content = "message")] pub enum SmtpError { - /// Error if we're using a SOCKS5 proxy. - #[serde(serialize_with = "ser_with_display")] - #[error("SOCKS5 error: {0}")] - SocksError(SocksError), - /// Error when communicating with SMTP server. - #[serde(serialize_with = "ser_with_display")] - #[error("SMTP error: {0}")] - SmtpError(AsyncSmtpError), /// Error when verifying a Yahoo email via HTTP requests. #[error("Yahoo error: {0}")] YahooError(YahooError), @@ -50,12 +41,21 @@ pub enum SmtpError { /// Error when verifying a Microsoft 365 email via HTTP request. #[error("Microsoft 365 API error: {0}")] Microsoft365Error(Microsoft365Error), -} - -impl From for SmtpError { - fn from(e: SocksError) -> Self { - SmtpError::SocksError(e) - } + /// Error from async-smtp crate. + #[error("SMTP error: {0}")] + #[serde(serialize_with = "ser_with_display")] + AsyncSmtpError(AsyncSmtpError), + /// I/O error. + #[error("I/O error: {0}")] + #[serde(serialize_with = "ser_with_display")] + IOError(std::io::Error), + /// Timeout error. + #[error("Timeout error: {0:?}")] + Timeout(Duration), + /// Socks5 proxy error. + #[error("Socks5 error: {0}")] + #[serde(serialize_with = "ser_with_display")] + Socks5(tokio_socks::Error), } impl From for SmtpError { @@ -82,6 +82,24 @@ impl From for SmtpError { } } +impl From for SmtpError { + fn from(e: AsyncSmtpError) -> Self { + SmtpError::AsyncSmtpError(e) + } +} + +impl From for SmtpError { + fn from(e: std::io::Error) -> Self { + SmtpError::IOError(e) + } +} + +impl From for SmtpError { + fn from(e: tokio_socks::Error) -> Self { + SmtpError::Socks5(e) + } +} + impl SmtpError { /// Get a human-understandable description of the error, in form of an enum /// SmtpErrorDesc. This only parses the following known errors: @@ -89,7 +107,7 @@ impl SmtpError { /// - IP needs reverse DNS pub fn get_description(&self) -> Option { match self { - SmtpError::SmtpError(_) => { + SmtpError::AsyncSmtpError(_) => { if parser::is_err_ip_blacklisted(self) { Some(SmtpErrorDesc::IpBlacklisted) } else if parser::is_err_needs_rdns(self) { diff --git a/core/src/smtp/gmail.rs b/core/src/smtp/gmail.rs index 221d2cafb..6769b156a 100644 --- a/core/src/smtp/gmail.rs +++ b/core/src/smtp/gmail.rs @@ -15,12 +15,12 @@ // along with this program. If not, see . use super::SmtpDetails; +use crate::EmailAddress; use crate::LOG_TARGET; use crate::{ smtp::http_api::create_client, util::{input_output::CheckEmailInput, ser_with_display::ser_with_display}, }; -use async_smtp::EmailAddress; use reqwest::Error as ReqwestError; use serde::Serialize; use thiserror::Error; diff --git a/core/src/smtp/mod.rs b/core/src/smtp/mod.rs index 14c208ec9..b319b4d9c 100644 --- a/core/src/smtp/mod.rs +++ b/core/src/smtp/mod.rs @@ -24,23 +24,21 @@ mod outlook; mod parser; mod yahoo; -use std::default::Default; - -use async_smtp::EmailAddress; -use hickory_proto::rr::Name; -use serde::{Deserialize, Serialize}; - +use crate::EmailAddress; use crate::{ util::input_output::CheckEmailInput, GmailVerifMethod, HotmailB2CVerifMethod, YahooVerifMethod, }; use connect::check_smtp_with_retry; -pub use error::*; +use hickory_proto::rr::Name; +use serde::{Deserialize, Serialize}; +use std::default::Default; pub use self::{ gmail::is_gmail, outlook::{is_hotmail, is_hotmail_b2b, is_hotmail_b2c}, yahoo::is_yahoo, }; +pub use error::*; #[derive(Debug, Default, Deserialize, PartialEq, Serialize)] pub struct SmtpConnection { @@ -163,7 +161,8 @@ pub async fn check_smtp( mod tests { use super::{check_smtp, SmtpConnection, SmtpError}; use crate::CheckEmailInputBuilder; - use async_smtp::{smtp::error::Error, EmailAddress}; + use crate::EmailAddress; + use async_smtp::error::Error; use hickory_proto::rr::Name; use std::{str::FromStr, time::Duration}; use tokio::runtime::Runtime; @@ -192,7 +191,7 @@ mod tests { }) ); match res { - Err(SmtpError::SmtpError(Error::Io(_))) => (), // ErrorKind == Timeout + Err(SmtpError::AsyncSmtpError(Error::Io(_))) => (), // ErrorKind == Timeout _ => panic!("check_smtp did not time out"), } } diff --git a/core/src/smtp/parser.rs b/core/src/smtp/parser.rs index 0d9c60b96..311f28b58 100644 --- a/core/src/smtp/parser.rs +++ b/core/src/smtp/parser.rs @@ -17,7 +17,8 @@ //! Parse the SMTP responses to get information about the email address. use super::error::SmtpError; -use async_smtp::{smtp::error::Error as AsyncSmtpError, EmailAddress}; +use crate::EmailAddress; +use async_smtp::error::Error as AsyncSmtpError; /// is_invalid checks for SMTP responses meaning that the email is invalid, /// i.e. that the mailbox doesn't exist. @@ -127,7 +128,7 @@ pub fn is_disabled_account(e: &str) -> bool { /// Check if the error is an IO "incomplete" error. pub fn is_err_io_errors(e: &SmtpError) -> bool { match e { - SmtpError::SmtpError(AsyncSmtpError::Io(err)) => err.to_string() == "incomplete", + SmtpError::AsyncSmtpError(AsyncSmtpError::Io(err)) => err.to_string() == "incomplete", _ => false, } } @@ -135,7 +136,7 @@ pub fn is_err_io_errors(e: &SmtpError) -> bool { /// Check if the IP is blacklisted. pub fn is_err_ip_blacklisted(e: &SmtpError) -> bool { let e = match e { - SmtpError::SmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => { + SmtpError::AsyncSmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => { // TODO We can use .to_string() after: // https://github.com/async-email/async-smtp/pull/53 r.message.join("; ").to_lowercase() @@ -205,7 +206,7 @@ pub fn is_err_ip_blacklisted(e: &SmtpError) -> bool { /// Check if the IP needs a reverse DNS. pub fn is_err_needs_rdns(e: &SmtpError) -> bool { let e = match e { - SmtpError::SmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => { + SmtpError::AsyncSmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => { // TODO We can use .to_string() after: // https://github.com/async-email/async-smtp/pull/53 r.message.join("; ").to_lowercase() @@ -227,11 +228,11 @@ pub fn is_err_needs_rdns(e: &SmtpError) -> bool { mod tests { use super::{is_err_ip_blacklisted, is_invalid}; - use crate::SmtpError::SmtpError; + use crate::EmailAddress; + use crate::SmtpError::AsyncSmtpError; use async_smtp::{ - smtp::error::Error, - smtp::response::{Category, Code, Detail, Response, Severity}, - EmailAddress, + error::Error, + response::{Category, Code, Detail, Response, Severity}, }; use std::str::FromStr; @@ -271,6 +272,6 @@ mod tests { ], )); - assert!(is_err_ip_blacklisted(&SmtpError(err))) + assert!(is_err_ip_blacklisted(&AsyncSmtpError(err))) } } diff --git a/core/src/syntax/mod.rs b/core/src/syntax/mod.rs index 4c5b8f2b0..fedac42e9 100644 --- a/core/src/syntax/mod.rs +++ b/core/src/syntax/mod.rs @@ -16,7 +16,7 @@ mod normalize; -use async_smtp::EmailAddress; +use crate::EmailAddress; use levenshtein::levenshtein; use normalize::normalize_email; use serde::{Deserialize, Serialize}; diff --git a/core/src/util/input_output.rs b/core/src/util/input_output.rs index 0eaa145b4..14f782fab 100644 --- a/core/src/util/input_output.rs +++ b/core/src/util/input_output.rs @@ -14,18 +14,80 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use std::str::FromStr; -use std::time::{Duration, SystemTime}; - -use async_smtp::{ClientSecurity, ClientTlsParameters}; -use chrono::{DateTime, Utc}; -use derive_builder::Builder; -use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; - use crate::misc::{MiscDetails, MiscError}; use crate::mx::{MxDetails, MxError}; use crate::smtp::{SmtpDebug, SmtpDetails, SmtpError, SmtpErrorDesc}; use crate::syntax::SyntaxDetails; +use crate::util::ser_with_display::ser_with_display; +use async_smtp::EmailAddress as AsyncSmtpEmailAddress; +use chrono::{DateTime, Utc}; +use derive_builder::Builder; +use serde::{ser::SerializeMap, Deserialize, Serialize, Serializer}; +use std::fmt::Display; +use std::str::FromStr; +use std::time::{Duration, SystemTime}; + +/// Wrapper around the `EmailAddress` from `async_smtp` to allow for +/// serialization and deserialization. +#[derive(Debug, Eq, PartialEq, Clone)] +pub struct EmailAddress(AsyncSmtpEmailAddress); + +impl Serialize for EmailAddress { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + ser_with_display(&self.0, serializer) + } +} + +impl<'de> Deserialize<'de> for EmailAddress { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(EmailAddress( + AsyncSmtpEmailAddress::from_str(&s).map_err(serde::de::Error::custom)?, + )) + } +} + +impl Display for EmailAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl FromStr for EmailAddress { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(EmailAddress(AsyncSmtpEmailAddress::from_str(s)?)) + } +} + +impl EmailAddress { + pub fn new(email: String) -> Result { + Ok(EmailAddress(AsyncSmtpEmailAddress::new(email)?)) + } + + pub fn into_inner(self) -> AsyncSmtpEmailAddress { + self.0 + } +} + +impl AsRef for EmailAddress { + fn as_ref(&self) -> &AsyncSmtpEmailAddress { + &self.0 + } +} + +impl AsRef for EmailAddress { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} /// Perform the email verification via a specified proxy. The usage of a proxy /// is optional. @@ -41,37 +103,6 @@ pub struct CheckEmailInputProxy { pub password: Option, } -/// Define how to apply TLS to a SMTP client connection. Will be converted into -/// async_smtp::ClientSecurity. -#[derive(Debug, Clone, Copy, Deserialize, Serialize)] -pub enum SmtpSecurity { - /// Insecure connection only (for testing purposes). - None, - /// Start with insecure connection and use `STARTTLS` when available. - Opportunistic, - /// Start with insecure connection and require `STARTTLS`. - Required, - /// Use TLS wrapped connection. - Wrapper, -} - -impl Default for SmtpSecurity { - fn default() -> Self { - Self::Opportunistic - } -} - -impl SmtpSecurity { - pub fn to_client_security(self, tls_params: ClientTlsParameters) -> ClientSecurity { - match self { - Self::None => ClientSecurity::None, - Self::Opportunistic => ClientSecurity::Opportunistic(tls_params), - Self::Required => ClientSecurity::Required(tls_params), - Self::Wrapper => ClientSecurity::Wrapper(tls_params), - } - } -} - /// Select how to verify Yahoo emails. #[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize, Serialize)] pub enum YahooVerifMethod { @@ -228,10 +259,6 @@ pub struct CheckEmailInput { /// /// Defaults to 1. pub retries: usize, - /// How to apply TLS to a SMTP client connection. - /// - /// Defaults to Opportunistic. - pub smtp_security: SmtpSecurity, /// The WebDriver address to use for headless verifications. /// /// Defaults to http://localhost:9515. @@ -256,7 +283,6 @@ impl Default for CheckEmailInput { hello_name: "gmail.com".into(), proxy: None, smtp_port: 25, - smtp_security: SmtpSecurity::default(), smtp_timeout: None, yahoo_verif_method: YahooVerifMethod::default(), gmail_verif_method: GmailVerifMethod::default(), @@ -411,7 +437,7 @@ impl Serialize for CheckEmailOutput { #[cfg(test)] mod tests { use super::{CheckEmailOutput, DebugDetails}; - use async_smtp::smtp::response::{Category, Code, Detail, Response, Severity}; + use async_smtp::response::{Category, Code, Detail, Response, Severity}; #[test] fn should_serialize_correctly() { @@ -433,7 +459,7 @@ mod tests { misc: Ok(super::MiscDetails::default()), mx: Ok(super::MxDetails::default()), syntax: super::SyntaxDetails::default(), - smtp: Err(super::SmtpError::SmtpError(r.into())), + smtp: Err(super::SmtpError::AsyncSmtpError(r.into())), debug: DebugDetails::default(), } } diff --git a/core/src/util/sentry.rs b/core/src/util/sentry.rs index 391cf2761..7aeab6516 100644 --- a/core/src/util/sentry.rs +++ b/core/src/util/sentry.rs @@ -24,7 +24,7 @@ use crate::misc::MiscError; use crate::mx::MxError; use crate::LOG_TARGET; use crate::{smtp::SmtpError, CheckEmailOutput}; -use async_smtp::smtp::error::Error as AsyncSmtpError; +use async_smtp::error::Error as AsyncSmtpError; use sentry::protocol::{Event, Exception, Level, Values}; use thiserror::Error; use tracing::{debug, info}; @@ -121,7 +121,7 @@ pub fn log_unknown_errors(result: &CheckEmailOutput, backend_name: &str) { (_, _, Err(err)) if err.get_description().is_some() => { // If the SMTP error is known, we don't track it in Sentry. } - (_, _, Err(SmtpError::SmtpError(AsyncSmtpError::Transient(response)))) + (_, _, Err(SmtpError::AsyncSmtpError(AsyncSmtpError::Transient(response)))) if skip_smtp_transient_errors(&response.message) => { // If the SMTP error is transient and known, we don't track it in