forked from reacherhq/check-if-email-exists
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Outlook HTTP API validation
- check the validity of Outlook/Office 365 email addresses via the method outlined [here](https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/). - run only via the `--outlook-use-api` flags (defaulting to `false`.) relates reacherhq#937
- Loading branch information
1 parent
49c8f5c
commit d048e25
Showing
4 changed files
with
92 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,19 +23,24 @@ use fantoccini::{ | |
ClientBuilder, Locator, | ||
}; | ||
use futures::TryFutureExt; | ||
use reqwest::Error as ReqwestError; | ||
use serde::Serialize; | ||
use serde_json::Map; | ||
|
||
use super::SmtpDetails; | ||
use crate::util::ser_with_display::ser_with_display; | ||
use crate::LOG_TARGET; | ||
use crate::{ | ||
smtp::http_api::create_client, util::ser_with_display::ser_with_display, CheckEmailInput, | ||
}; | ||
|
||
#[derive(Debug, Serialize)] | ||
pub enum HotmailError { | ||
#[serde(serialize_with = "ser_with_display")] | ||
Cmd(CmdError), | ||
#[serde(serialize_with = "ser_with_display")] | ||
NewSession(NewSessionError), | ||
#[serde(serialize_with = "ser_with_display")] | ||
ReqwestError(ReqwestError), | ||
} | ||
|
||
impl From<CmdError> for HotmailError { | ||
|
@@ -50,6 +55,12 @@ impl From<NewSessionError> for HotmailError { | |
} | ||
} | ||
|
||
impl From<ReqwestError> for HotmailError { | ||
fn from(error: ReqwestError) -> Self { | ||
HotmailError::ReqwestError(error) | ||
} | ||
} | ||
|
||
/// Check if a Hotmail/Outlook email exists by connecting to the password | ||
/// recovery page https://account.live.com/password/reset using a headless | ||
/// browser. Make sure you have a WebDriver server running locally before | ||
|
@@ -140,9 +151,56 @@ pub async fn check_password_recovery( | |
}) | ||
} | ||
|
||
/// Convert an email address to its corresponding OneDrive URL. | ||
fn get_onedrive_url(email_address: &str) -> String { | ||
let (username, domain) = email_address | ||
.split_once('@') | ||
.expect("Email address syntax already validated."); | ||
let (tenant, _) = domain | ||
.split_once('.') | ||
.expect("Email domain syntax already validated."); | ||
|
||
format!( | ||
"https://{}-my.sharepoint.com/personal/{}_{}/_layouts/15/onedrive.aspx", | ||
tenant, | ||
username.replace('.', "_"), | ||
domain.replace('.', "_"), | ||
) | ||
} | ||
|
||
/// Use HTTP request to verify if an Outlook email address exists. | ||
/// See: <https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/> | ||
pub async fn check_outlook_api( | ||
to_email: &EmailAddress, | ||
input: &CheckEmailInput, | ||
) -> Result<SmtpDetails, HotmailError> { | ||
let url = get_onedrive_url(to_email.as_ref()); | ||
|
||
let response = create_client(input, "outlook")? | ||
.head(url) | ||
.query(&[("email", to_email)]) | ||
.send() | ||
.await?; | ||
|
||
let email_exists = response.status() == 403; | ||
|
||
log::debug!( | ||
target: LOG_TARGET, | ||
"[email={}] outlook response: {:?}", | ||
to_email, | ||
response | ||
); | ||
|
||
Ok(SmtpDetails { | ||
can_connect_smtp: true, | ||
is_deliverable: email_exists, | ||
..Default::default() | ||
}) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::check_password_recovery; | ||
use super::{check_password_recovery, get_onedrive_url}; | ||
use async_smtp::EmailAddress; | ||
use async_std::prelude::FutureExt; | ||
use std::str::FromStr; | ||
|
@@ -193,4 +251,12 @@ mod tests { | |
let f = f1.try_join(f2).await; | ||
assert!(f.is_ok(), "{:?}", f); | ||
} | ||
|
||
#[test] | ||
fn test_onedrive_url() { | ||
let email_address = "[email protected]"; | ||
let expected = "https://acmecomputercompany-my.sharepoint.com/personal/lightmand_acmecomputercompany_com/_layouts/15/onedrive.aspx"; | ||
|
||
assert_eq!(expected, get_onedrive_url(email_address)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters