diff --git a/core/src/smtp/connect.rs b/core/src/smtp/connect.rs index 058847603..97a579dcd 100644 --- a/core/src/smtp/connect.rs +++ b/core/src/smtp/connect.rs @@ -29,7 +29,7 @@ use std::time::Duration; use trust_dns_proto::rr::Name; -use super::parser; +use super::{gmail::is_gmail, outlook::is_hotmail, parser, yahoo::is_yahoo}; use super::{SmtpDetails, SmtpError}; use crate::util::{constants::LOG_TARGET, input_output::CheckEmailInput}; @@ -220,7 +220,14 @@ async fn email_deliverable( async fn smtp_is_catch_all( smtp_transport: &mut SmtpTransport, domain: &str, + host: &Name, ) -> Result { + // Skip catch-all check for known providers. + let host = host.to_string(); + if is_gmail(&host) || is_hotmail(&host) || is_yahoo(&host) { + return Ok(false); + } + // Create a random 15-char alphanumerical string. let mut rng = SmallRng::from_entropy(); let random_email: String = iter::repeat(()) @@ -249,7 +256,7 @@ async fn create_smtp_future( // Ok(SmtpDetails { can_connect_smtp: false, ... }). let mut smtp_transport = connect_to_host(host, port, input).await?; - let is_catch_all = smtp_is_catch_all(&mut smtp_transport, domain) + let is_catch_all = smtp_is_catch_all(&mut smtp_transport, domain, host) .await .unwrap_or(false); let deliverability = if is_catch_all { diff --git a/core/src/smtp/error.rs b/core/src/smtp/error.rs index d0982d573..d042135e9 100644 --- a/core/src/smtp/error.rs +++ b/core/src/smtp/error.rs @@ -16,8 +16,8 @@ use super::gmail::GmailError; #[cfg(feature = "headless")] -use super::microsoft::hotmail::HotmailError; -use super::microsoft::microsoft365::Microsoft365Error; +use super::outlook::hotmail::HotmailError; +use super::outlook::microsoft365::Microsoft365Error; use super::parser; use super::yahoo::YahooError; use crate::util::ser_with_display::ser_with_display; diff --git a/core/src/smtp/gmail.rs b/core/src/smtp/gmail.rs index c4250e5f2..8ec9c8bca 100644 --- a/core/src/smtp/gmail.rs +++ b/core/src/smtp/gmail.rs @@ -76,6 +76,11 @@ pub async fn check_gmail( }) } +/// Check if the MX host is from Gmail. +pub fn is_gmail(host: &str) -> bool { + host.to_lowercase().ends_with(".google.com.") +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/core/src/smtp/microsoft/mod.rs b/core/src/smtp/microsoft/mod.rs deleted file mode 100644 index 35cff51f9..000000000 --- a/core/src/smtp/microsoft/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -#[cfg(feature = "headless")] -pub mod hotmail; -pub mod microsoft365; diff --git a/core/src/smtp/mod.rs b/core/src/smtp/mod.rs index 36648fd19..0b56d4c4e 100644 --- a/core/src/smtp/mod.rs +++ b/core/src/smtp/mod.rs @@ -18,7 +18,7 @@ mod connect; mod error; mod gmail; mod http_api; -mod microsoft; +mod outlook; mod parser; mod yahoo; @@ -32,6 +32,12 @@ use crate::{util::input_output::CheckEmailInput, LOG_TARGET}; use connect::check_smtp_with_retry; pub use error::*; +use self::{ + gmail::is_gmail, + outlook::{is_hotmail, is_outlook}, + yahoo::is_yahoo, +}; + /// Details that we gathered from connecting to this email via SMTP #[derive(Debug, Default, Deserialize, Serialize)] pub struct SmtpDetails { @@ -68,19 +74,18 @@ pub async fn check_smtp( ))); } - // FIXME Is this `contains` too lenient? - if input.yahoo_use_api && host_lowercase.contains("yahoo") { + if input.yahoo_use_api && is_yahoo(&host_lowercase) { return yahoo::check_yahoo(to_email, input) .await .map_err(|err| err.into()); } - if input.gmail_use_api && host_lowercase.ends_with(".google.com.") { + if input.gmail_use_api && is_gmail(&host_lowercase) { return gmail::check_gmail(to_email, input) .await .map_err(|err| err.into()); } - if input.microsoft365_use_api && host_lowercase.ends_with(".mail.protection.outlook.com.") { - match microsoft::microsoft365::check_microsoft365_api(to_email, input).await { + if input.microsoft365_use_api && is_outlook(&host_lowercase) { + match outlook::microsoft365::check_microsoft365_api(to_email, input).await { Ok(Some(smtp_details)) => return Ok(smtp_details), // Continue in the event of an error/ambiguous result. Err(err) => { @@ -99,16 +104,8 @@ pub async fn check_smtp( // The password recovery page do not always work with Microsoft 365 // addresses. So we only test with @hotmail and @outlook addresses. // ref: https://github.com/reacherhq/check-if-email-exists/issues/1185 - // - // After some testing, I got: - // - *@outlook.com -> `outlook-com.olc.protection.outlook.com.` - // - *@outlook.fr -> `eur.olc.protection.outlook.com.` - // - *@hotmail.com -> `hotmail-com.olc.protection.outlook.com.` - // - *@hotmail.fr -> `eur.olc.protection.outlook.com.` - // - // So it seems that outlook/hotmail addresses end with `olc.protection.outlook.com.` - if host_lowercase.ends_with("olc.protection.outlook.com.") { - return microsoft::hotmail::check_password_recovery(to_email, webdriver) + if is_hotmail(&host_lowercase) { + return outlook::hotmail::check_password_recovery(to_email, webdriver) .await .map_err(|err| err.into()); } diff --git a/core/src/smtp/microsoft/hotmail.rs b/core/src/smtp/outlook/hotmail.rs similarity index 100% rename from core/src/smtp/microsoft/hotmail.rs rename to core/src/smtp/outlook/hotmail.rs diff --git a/core/src/smtp/microsoft/microsoft365.rs b/core/src/smtp/outlook/microsoft365.rs similarity index 100% rename from core/src/smtp/microsoft/microsoft365.rs rename to core/src/smtp/outlook/microsoft365.rs diff --git a/core/src/smtp/outlook/mod.rs b/core/src/smtp/outlook/mod.rs new file mode 100644 index 000000000..3c8c9c077 --- /dev/null +++ b/core/src/smtp/outlook/mod.rs @@ -0,0 +1,25 @@ +#[cfg(feature = "headless")] +pub mod hotmail; +pub mod microsoft365; + +/// Check if a MX host is from outlook (includes @hotmail.*, @outlook.* and +/// all Microsoft 365 addresses). +pub fn is_outlook(host: &str) -> bool { + host.to_lowercase() + .ends_with(".mail.protection.outlook.com.") +} + +/// Check if a MX host is an @hotmail.* or @outlook.* email. +/// +/// After some testing, I got: +/// - *@outlook.com -> `outlook-com.olc.protection.outlook.com.` +/// - *@outlook.fr -> `eur.olc.protection.outlook.com.` +/// - *@hotmail.com -> `hotmail-com.olc.protection.outlook.com.` +/// - *@hotmail.fr -> `eur.olc.protection.outlook.com.` +/// - *@hotmail.nl -> `eur.olc.protection.outlook.com.` +/// +/// So it seems that outlook/hotmail addresses end with `olc.protection.outlook.com.` +pub fn is_hotmail(host: &str) -> bool { + host.to_lowercase() + .ends_with(".mail.protection.outlook.com.") +} diff --git a/core/src/smtp/yahoo.rs b/core/src/smtp/yahoo.rs index 91b09af50..494d9a90f 100644 --- a/core/src/smtp/yahoo.rs +++ b/core/src/smtp/yahoo.rs @@ -224,3 +224,11 @@ pub async fn check_yahoo( ..Default::default() }) } + +/// Check if the MX host is from Yahoo. +/// Examples: +/// - mta7.am0.yahoodns.net. +/// - mx-eu.mail.am0.yahoodns.net. +pub fn is_yahoo(host: &str) -> bool { + host.to_lowercase().ends_with(".yahoodns.net.") +}