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