From af2d200dc337365421d79e646fc52d8c9301fdbe Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaury1729@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:20:42 +0200 Subject: [PATCH 1/2] feat(core): Skip catch-all for known domains --- core/src/smtp/connect.rs | 11 +++++++++-- core/src/smtp/error.rs | 4 ++-- core/src/smtp/gmail.rs | 5 +++++ core/src/smtp/microsoft/mod.rs | 3 --- core/src/smtp/mod.rs | 15 ++++++++------- core/src/smtp/{microsoft => outlook}/hotmail.rs | 0 .../smtp/{microsoft => outlook}/microsoft365.rs | 0 core/src/smtp/outlook/mod.rs | 9 +++++++++ core/src/smtp/yahoo.rs | 8 ++++++++ 9 files changed, 41 insertions(+), 14 deletions(-) delete mode 100644 core/src/smtp/microsoft/mod.rs rename core/src/smtp/{microsoft => outlook}/hotmail.rs (100%) rename core/src/smtp/{microsoft => outlook}/microsoft365.rs (100%) create mode 100644 core/src/smtp/outlook/mod.rs diff --git a/core/src/smtp/connect.rs b/core/src/smtp/connect.rs index 058847603..3d9fc129f 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_outlook, 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_outlook(&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..69d0564f1 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,8 @@ use crate::{util::input_output::CheckEmailInput, LOG_TARGET}; use connect::check_smtp_with_retry; pub use error::*; +use self::{gmail::is_gmail, outlook::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 +70,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) => { @@ -108,7 +109,7 @@ pub async fn check_smtp( // // 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) + 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..92a80a77f --- /dev/null +++ b/core/src/smtp/outlook/mod.rs @@ -0,0 +1,9 @@ +#[cfg(feature = "headless")] +pub mod hotmail; +pub mod microsoft365; + +/// Check if a MX host is from outlook. +pub fn is_outlook(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.") +} From 4fa2c84b6b9f317227fee9f2e4eb8f3d365e2177 Mon Sep 17 00:00:00 2001 From: Amaury <1293565+amaury1729@users.noreply.github.com> Date: Fri, 6 Oct 2023 22:37:44 +0200 Subject: [PATCH 2/2] Only skip catch-all for hotmail --- core/src/smtp/connect.rs | 4 ++-- core/src/smtp/mod.rs | 16 ++++++---------- core/src/smtp/outlook/mod.rs | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/core/src/smtp/connect.rs b/core/src/smtp/connect.rs index 3d9fc129f..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::{gmail::is_gmail, outlook::is_outlook, parser, yahoo::is_yahoo}; +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}; @@ -224,7 +224,7 @@ async fn smtp_is_catch_all( ) -> Result { // Skip catch-all check for known providers. let host = host.to_string(); - if is_gmail(&host) || is_outlook(&host) || is_yahoo(&host) { + if is_gmail(&host) || is_hotmail(&host) || is_yahoo(&host) { return Ok(false); } diff --git a/core/src/smtp/mod.rs b/core/src/smtp/mod.rs index 69d0564f1..0b56d4c4e 100644 --- a/core/src/smtp/mod.rs +++ b/core/src/smtp/mod.rs @@ -32,7 +32,11 @@ use crate::{util::input_output::CheckEmailInput, LOG_TARGET}; use connect::check_smtp_with_retry; pub use error::*; -use self::{gmail::is_gmail, outlook::is_outlook, yahoo::is_yahoo}; +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)] @@ -100,15 +104,7 @@ 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.") { + 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/outlook/mod.rs b/core/src/smtp/outlook/mod.rs index 92a80a77f..3c8c9c077 100644 --- a/core/src/smtp/outlook/mod.rs +++ b/core/src/smtp/outlook/mod.rs @@ -2,8 +2,24 @@ pub mod hotmail; pub mod microsoft365; -/// Check if a MX host is from outlook. +/// 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.") +}