valid_email_checker 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +24 -0
- data/Cargo.lock +2940 -0
- data/Cargo.toml +22 -0
- data/LICENSE.txt +661 -0
- data/NOTICE.md +56 -0
- data/README.md +316 -0
- data/exe/valid_email_checker +148 -0
- data/ext/valid_email_checker/Cargo.toml +27 -0
- data/ext/valid_email_checker/extconf.rb +26 -0
- data/ext/valid_email_checker/src/lib.rs +371 -0
- data/ext/valid_email_checker/src/nogvl.rs +64 -0
- data/ext/valid_email_checker/vendor/UPSTREAM.json +9 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/Cargo.toml +52 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.AGPL +661 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/LICENSE.md +11 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/README.md +175 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/haveibeenpwned.rs +70 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/lib.rs +281 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/b2c.txt +96640 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/gravatar.rs +60 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/mod.rs +124 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/misc/roles.txt +944 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/mx/mod.rs +165 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.json +28 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/rules.rs +105 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/connect.rs +396 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/error.rs +144 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/gmail.rs +99 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/headless.rs +82 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/http_api.rs +27 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/mod.rs +234 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/headless.rs +181 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/microsoft365.rs +109 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/outlook/mod.rs +2 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/parser.rs +291 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/verif_method.rs +531 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/api.rs +174 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/headless.rs +188 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/smtp/yahoo/mod.rs +62 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/mod.rs +199 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/syntax/normalize.rs +70 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/input_output.rs +353 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/mod.rs +20 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/sentry.rs +173 -0
- data/ext/valid_email_checker/vendor/check-if-email-exists/src/util/ser_with_display.rs +28 -0
- data/lib/valid_email_checker/configuration.rb +216 -0
- data/lib/valid_email_checker/errors.rb +32 -0
- data/lib/valid_email_checker/result.rb +241 -0
- data/lib/valid_email_checker/syntax.rb +72 -0
- data/lib/valid_email_checker/version.rb +5 -0
- data/lib/valid_email_checker.rb +217 -0
- metadata +118 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// check-if-email-exists
|
|
2
|
+
// Copyright (C) 2018-2023 Reacher
|
|
3
|
+
|
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
|
5
|
+
// it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
// by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
// (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
// GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
use std::{pin::Pin, thread::sleep, time::Duration};
|
|
18
|
+
|
|
19
|
+
use fantoccini::{error::CmdError, Locator};
|
|
20
|
+
use futures::{future::select_ok, Future, TryFutureExt};
|
|
21
|
+
|
|
22
|
+
use crate::{
|
|
23
|
+
smtp::{
|
|
24
|
+
headless::{create_headless_client, HeadlessError},
|
|
25
|
+
SmtpDetails,
|
|
26
|
+
},
|
|
27
|
+
WebdriverConfig, LOG_TARGET,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/// Check if a Hotmail/Outlook email exists by connecting to the password
|
|
31
|
+
/// recovery page https://account.live.com/password/reset using a headless
|
|
32
|
+
/// browser. Make sure you have a WebDriver server running locally before
|
|
33
|
+
/// running this, or this will error.
|
|
34
|
+
pub async fn check_password_recovery(
|
|
35
|
+
to_email: &str,
|
|
36
|
+
webdriver: &str,
|
|
37
|
+
webdriver_config: &WebdriverConfig,
|
|
38
|
+
) -> Result<SmtpDetails, HeadlessError> {
|
|
39
|
+
let to_email = to_email.to_string();
|
|
40
|
+
tracing::debug!(
|
|
41
|
+
target: LOG_TARGET,
|
|
42
|
+
email=to_email,
|
|
43
|
+
"Using Hotmail password recovery in headless navigator"
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
let c = create_headless_client(webdriver, webdriver_config).await?;
|
|
47
|
+
|
|
48
|
+
// Navigate to Microsoft password recovery page.
|
|
49
|
+
c.goto("https://account.live.com/password/reset").await?;
|
|
50
|
+
|
|
51
|
+
// Wait for network/javascript/dom to make the input-box available
|
|
52
|
+
// and click it.
|
|
53
|
+
let input = c.wait().for_element(Locator::Id("iSigninName")).await?;
|
|
54
|
+
// Sometimes I get "input ... is not reachable by keyboard". Addind this
|
|
55
|
+
// small sleep helps.
|
|
56
|
+
sleep(Duration::from_millis(200));
|
|
57
|
+
input.send_keys(to_email.as_str()).await?;
|
|
58
|
+
|
|
59
|
+
// Click on "Next"
|
|
60
|
+
c.find(Locator::Id("resetPwdHipAction"))
|
|
61
|
+
.await?
|
|
62
|
+
.click()
|
|
63
|
+
.await?;
|
|
64
|
+
|
|
65
|
+
// "Try entering your Microsoft account again. We don't recognise this one." means the account does not exist.
|
|
66
|
+
let f1 = c
|
|
67
|
+
.wait()
|
|
68
|
+
.for_element(Locator::Id("pMemberNameErr"))
|
|
69
|
+
.and_then(|_| async { Ok(false) });
|
|
70
|
+
// "Try entering your Microsoft account again. We don't recognise this one."
|
|
71
|
+
let f2 = c
|
|
72
|
+
.wait()
|
|
73
|
+
.for_element(Locator::Id("iSigninNameError"))
|
|
74
|
+
.and_then(|_| async { Ok(false) });
|
|
75
|
+
// "We need to verify your identity" means that the account exists.
|
|
76
|
+
let f3 = c
|
|
77
|
+
.wait()
|
|
78
|
+
.for_element(Locator::Id("iSelectProofTitle"))
|
|
79
|
+
.and_then(|_| async { Ok(true) });
|
|
80
|
+
// "Enter the code generated by your authenticator app..."
|
|
81
|
+
let f4 = c
|
|
82
|
+
.wait()
|
|
83
|
+
.for_element(Locator::Id("iEnterVerification"))
|
|
84
|
+
.and_then(|_| async { Ok(true) });
|
|
85
|
+
|
|
86
|
+
let vec = vec![
|
|
87
|
+
Box::pin(f1) as Pin<Box<dyn Future<Output = Result<bool, CmdError>> + Send>>,
|
|
88
|
+
Box::pin(f2),
|
|
89
|
+
Box::pin(f3),
|
|
90
|
+
Box::pin(f4),
|
|
91
|
+
];
|
|
92
|
+
let (is_deliverable, _) = select_ok(vec).await?;
|
|
93
|
+
|
|
94
|
+
if is_deliverable {
|
|
95
|
+
tracing::debug!(
|
|
96
|
+
target: LOG_TARGET,
|
|
97
|
+
email=to_email,
|
|
98
|
+
exists=is_deliverable,
|
|
99
|
+
"Did not find error message in password recovery, email exists"
|
|
100
|
+
);
|
|
101
|
+
} else {
|
|
102
|
+
tracing::debug!(
|
|
103
|
+
target: LOG_TARGET,
|
|
104
|
+
email=to_email,
|
|
105
|
+
exists=is_deliverable,
|
|
106
|
+
"Found error message in password recovery, email does not exist"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
c.close().await?;
|
|
111
|
+
|
|
112
|
+
tracing::debug!(
|
|
113
|
+
target: LOG_TARGET,
|
|
114
|
+
email=to_email,
|
|
115
|
+
exists=is_deliverable,
|
|
116
|
+
"Password recovery check result"
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
Ok(SmtpDetails {
|
|
120
|
+
can_connect_smtp: true,
|
|
121
|
+
has_full_inbox: false,
|
|
122
|
+
is_catch_all: false,
|
|
123
|
+
is_deliverable,
|
|
124
|
+
is_disabled: false,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[cfg(test)]
|
|
129
|
+
mod tests {
|
|
130
|
+
use super::check_password_recovery;
|
|
131
|
+
use crate::WebdriverConfig;
|
|
132
|
+
use futures::future::join;
|
|
133
|
+
|
|
134
|
+
// Ignoring this test as it requires a local process of WebDriver running on
|
|
135
|
+
// "http://localhost:9515". To debug the headless password recovery page,
|
|
136
|
+
// run chromedriver and remove the "#[ignore]".
|
|
137
|
+
// Also see: https://github.com/jonhoo/fantoccini
|
|
138
|
+
#[tokio::test]
|
|
139
|
+
#[ignore = "Run a webdriver server locally to test this"]
|
|
140
|
+
async fn test_hotmail_address() {
|
|
141
|
+
// Run 10 headless sessions with dummy emails.
|
|
142
|
+
// It should not error.
|
|
143
|
+
for _ in 0..10 {
|
|
144
|
+
// This email does not exist.
|
|
145
|
+
let res = check_password_recovery(
|
|
146
|
+
"test42134@hotmail.com",
|
|
147
|
+
"http://localhost:9515",
|
|
148
|
+
&WebdriverConfig::default(),
|
|
149
|
+
)
|
|
150
|
+
.await
|
|
151
|
+
.unwrap();
|
|
152
|
+
assert!(!res.is_deliverable);
|
|
153
|
+
|
|
154
|
+
// This email does exist.
|
|
155
|
+
let res = check_password_recovery(
|
|
156
|
+
"test@hotmail.com",
|
|
157
|
+
"http://localhost:9515",
|
|
158
|
+
&WebdriverConfig::default(),
|
|
159
|
+
)
|
|
160
|
+
.await
|
|
161
|
+
.unwrap();
|
|
162
|
+
assert!(res.is_deliverable);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// This test tests that we can run 2 instances of check_password_recovery.
|
|
167
|
+
// This will only work with chromedriver (which supports parallel cleints),
|
|
168
|
+
// but will fail with geckodriver.
|
|
169
|
+
// ref: https://github.com/jonhoo/fantoccini/issues/111#issuecomment-727650629
|
|
170
|
+
#[tokio::test]
|
|
171
|
+
#[ignore = "Run a webdriver server locally to test this"]
|
|
172
|
+
async fn test_parallel() {
|
|
173
|
+
let webdriver_config = WebdriverConfig::default();
|
|
174
|
+
// This email does not exist.
|
|
175
|
+
let f1 = check_password_recovery("foo@bar.baz", "http://localhost:9515", &webdriver_config);
|
|
176
|
+
let f2 = check_password_recovery("foo@bar.baz", "http://localhost:9515", &webdriver_config);
|
|
177
|
+
|
|
178
|
+
let f = join(f1, f2).await;
|
|
179
|
+
assert!(f.0.is_ok(), "{:?}", f);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// check-if-email-exists
|
|
2
|
+
// Copyright (C) 2018-2023 Reacher
|
|
3
|
+
|
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
|
5
|
+
// it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
// by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
// (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
// GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
use crate::{
|
|
18
|
+
smtp::{http_api::create_client, SmtpDetails},
|
|
19
|
+
util::ser_with_display::ser_with_display,
|
|
20
|
+
CheckEmailInput, LOG_TARGET,
|
|
21
|
+
};
|
|
22
|
+
use async_smtp::EmailAddress;
|
|
23
|
+
use reqwest::Error as ReqwestError;
|
|
24
|
+
use serde::Serialize;
|
|
25
|
+
use thiserror::Error;
|
|
26
|
+
|
|
27
|
+
#[derive(Debug, Error, Serialize)]
|
|
28
|
+
pub enum Microsoft365Error {
|
|
29
|
+
#[serde(serialize_with = "ser_with_display")]
|
|
30
|
+
#[error("Reqwest error: {0}")]
|
|
31
|
+
ReqwestError(ReqwestError),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
impl From<ReqwestError> for Microsoft365Error {
|
|
35
|
+
fn from(error: ReqwestError) -> Self {
|
|
36
|
+
Microsoft365Error::ReqwestError(error)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Convert an email address to its corresponding OneDrive URL.
|
|
41
|
+
#[allow(dead_code)]
|
|
42
|
+
fn get_onedrive_url(email_address: &str) -> String {
|
|
43
|
+
let (username, domain) = email_address
|
|
44
|
+
.split_once('@')
|
|
45
|
+
.expect("Email address syntax already validated.");
|
|
46
|
+
let (tenant, _) = domain
|
|
47
|
+
.split_once('.')
|
|
48
|
+
.expect("Email domain syntax already validated.");
|
|
49
|
+
|
|
50
|
+
format!(
|
|
51
|
+
"https://{}-my.sharepoint.com/personal/{}_{}/_layouts/15/onedrive.aspx",
|
|
52
|
+
tenant,
|
|
53
|
+
username.replace('.', "_"),
|
|
54
|
+
domain.replace('.', "_"),
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Use a HTTP request to verify if an Microsoft 365 email address exists.
|
|
59
|
+
///
|
|
60
|
+
/// See
|
|
61
|
+
/// [this article](<https://www.trustedsec.com/blog/achieving-passive-user-enumeration-with-onedrive/>)
|
|
62
|
+
/// for details on the underlying principles.
|
|
63
|
+
///
|
|
64
|
+
/// Note that a positive response from this function is (at present) considered
|
|
65
|
+
/// a reliable indicator that an email-address is valid. However, a negative
|
|
66
|
+
/// response is ambigious: the email address may or may not be valid but this
|
|
67
|
+
/// cannot be determined by the method outlined here.
|
|
68
|
+
#[allow(dead_code)]
|
|
69
|
+
pub async fn check_microsoft365_api(
|
|
70
|
+
to_email: &EmailAddress,
|
|
71
|
+
input: &CheckEmailInput,
|
|
72
|
+
) -> Result<Option<SmtpDetails>, Microsoft365Error> {
|
|
73
|
+
let url = get_onedrive_url(to_email.as_ref());
|
|
74
|
+
|
|
75
|
+
let response = create_client(input, "microsoft365")?
|
|
76
|
+
.head(url)
|
|
77
|
+
.send()
|
|
78
|
+
.await?;
|
|
79
|
+
|
|
80
|
+
tracing::debug!(
|
|
81
|
+
target: LOG_TARGET,
|
|
82
|
+
email = %to_email,
|
|
83
|
+
response = ?response,
|
|
84
|
+
"microsoft365 response"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
if response.status() == 403 {
|
|
88
|
+
Ok(Some(SmtpDetails {
|
|
89
|
+
can_connect_smtp: true,
|
|
90
|
+
is_deliverable: true,
|
|
91
|
+
..Default::default()
|
|
92
|
+
}))
|
|
93
|
+
} else {
|
|
94
|
+
Ok(None)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[cfg(test)]
|
|
99
|
+
mod tests {
|
|
100
|
+
use super::*;
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn test_onedrive_url() {
|
|
104
|
+
let email_address = "lightmand@acmecomputercompany.com";
|
|
105
|
+
let expected = "https://acmecomputercompany-my.sharepoint.com/personal/lightmand_acmecomputercompany_com/_layouts/15/onedrive.aspx";
|
|
106
|
+
|
|
107
|
+
assert_eq!(expected, get_onedrive_url(email_address));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Reacher - Email Verification
|
|
2
|
+
// Copyright (C) 2018-2023 Reacher
|
|
3
|
+
|
|
4
|
+
// This program is free software: you can redistribute it and/or modify
|
|
5
|
+
// it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
// by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
// (at your option) any later version.
|
|
8
|
+
|
|
9
|
+
// This program is distributed in the hope that it will be useful,
|
|
10
|
+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
// GNU Affero General Public License for more details.
|
|
13
|
+
|
|
14
|
+
// You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
|
|
17
|
+
//! Parse the SMTP responses to get information about the email address.
|
|
18
|
+
|
|
19
|
+
use super::error::SmtpError;
|
|
20
|
+
use crate::EmailAddress;
|
|
21
|
+
use async_smtp::error::Error as AsyncSmtpError;
|
|
22
|
+
|
|
23
|
+
/// is_invalid checks for SMTP responses meaning that the email is invalid,
|
|
24
|
+
/// i.e. that the mailbox doesn't exist.
|
|
25
|
+
pub fn is_invalid(e: &str, email: &EmailAddress) -> bool {
|
|
26
|
+
// 550 Address rejected
|
|
27
|
+
// 550 5.1.1 : Recipient address rejected
|
|
28
|
+
// 550 5.1.1 : Recipient address rejected: User unknown in virtual alias table
|
|
29
|
+
// 550 5.1.1 <EMAIL: Recipient address rejected: User unknown in relay recipient table
|
|
30
|
+
e.contains("address rejected")
|
|
31
|
+
// 550 5.1.1 : Unrouteable address
|
|
32
|
+
|| e.contains("unrouteable")
|
|
33
|
+
// 550 5.1.1 : The email account that you tried to reach does not exist
|
|
34
|
+
|| e.contains("does not exist")
|
|
35
|
+
// 550 invalid address
|
|
36
|
+
// 550 User not local or invalid address – Relay denied
|
|
37
|
+
|| e.contains("invalid address")
|
|
38
|
+
// 5.1.1 Invalid email address
|
|
39
|
+
|| e.contains("invalid email address")
|
|
40
|
+
// 550 Invalid recipient
|
|
41
|
+
|| e.contains("invalid recipient")
|
|
42
|
+
|| e.contains("may not exist")
|
|
43
|
+
|| e.contains("recipient invalid")
|
|
44
|
+
// 550 5.1.1 : Recipient rejected
|
|
45
|
+
|| e.contains("recipient rejected")
|
|
46
|
+
// permanent: 5.1.1 Unknown recipient address
|
|
47
|
+
|| e.contains("unknown recipient address")
|
|
48
|
+
// 554 Unknown Recipient (#5.1.1) (on @parkwayhonda.com)
|
|
49
|
+
|| e.contains("unknown recipient")
|
|
50
|
+
|| e.contains("undeliverable")
|
|
51
|
+
// 550 User unknown
|
|
52
|
+
// 550 5.1.1 <EMAIL> User unknown
|
|
53
|
+
// 550 recipient address rejected: user unknown in local recipient table
|
|
54
|
+
|| e.contains("user unknown")
|
|
55
|
+
// 550 Unknown user
|
|
56
|
+
|| e.contains("unknown user")
|
|
57
|
+
// 5.1.1 Recipient unknown <EMAIL>
|
|
58
|
+
|| e.contains("recipient unknown")
|
|
59
|
+
// 550 5.1.1 No such user - pp
|
|
60
|
+
// 550 No such user here
|
|
61
|
+
|| e.contains("no such user")
|
|
62
|
+
// permanent: 5.1.1 MXIN501 mailbox <EMAIL> unknown (on @virginmedia.com)
|
|
63
|
+
|| e.contains(format!("mailbox {email} unknown").as_str())
|
|
64
|
+
// 550 5.1.1 : Mailbox not found
|
|
65
|
+
// 550 Unknown address error ‘MAILBOX NOT FOUND’
|
|
66
|
+
|| e.contains("mailbox not found")
|
|
67
|
+
// 550 5.1.1 : Invalid mailbox
|
|
68
|
+
|| e.contains("invalid mailbox")
|
|
69
|
+
// 550 5.1.1 Sorry, no mailbox here by that name
|
|
70
|
+
|| e.contains("no mailbox")
|
|
71
|
+
// 5.2.0 No such mailbox
|
|
72
|
+
|| e.contains("no such mailbox")
|
|
73
|
+
// 550 Requested action not taken: mailbox unavailable
|
|
74
|
+
|| e.contains("mailbox unavailable")
|
|
75
|
+
// 5.5.0 Requested actions not taken as the mailbox is unavailable (on @etu.uca.fr)
|
|
76
|
+
|| e.contains("mailbox is unavailable")
|
|
77
|
+
// 550 5.1.1 Is not a valid mailbox
|
|
78
|
+
|| e.contains("not a valid mailbox")
|
|
79
|
+
// No such recipient here
|
|
80
|
+
|| e.contains("no such recipient")
|
|
81
|
+
// 554 delivery error: This user doesn’t have an account
|
|
82
|
+
|| e.contains("have an account")
|
|
83
|
+
// permanent: Unknown local part <USER> in <USER@flabeg.com> (on @flabeg.com)
|
|
84
|
+
|| e.contains("unknown local part")
|
|
85
|
+
// 5.1.1 RCP-P1 Domain facebook.com no longer available https://www.facebook.com/postmaster/response_codes?ip=3.80.111.155#RCP-P1
|
|
86
|
+
|| e.contains("no longer available")
|
|
87
|
+
// permanent: RCPT (<EMAIL>) dosn't exist (on @hgy.ooo, @stigpods.com.cn)
|
|
88
|
+
|| e.contains("dosn't exist") // sic! typo is intentional
|
|
89
|
+
// 5.1.1 <EMAIL>: Email address could not be found, or was misspelled (G8) (on @biotech-calendar.com, @invoicefactoring.com)
|
|
90
|
+
|| e.contains("could not be found")
|
|
91
|
+
// No such person at this address (on @aconsa.com.mx)
|
|
92
|
+
|| e.contains("no such person")
|
|
93
|
+
// Callout verification failed: 550 No Such User Here (on @medipro.co.uk)
|
|
94
|
+
|| e.contains("no such user")
|
|
95
|
+
// 5.1.1 <EMAIL> Address Error (on @lucidity.co.za)
|
|
96
|
+
|| e.contains("address error")
|
|
97
|
+
// E-mail address is not handled by this system (on @kaimayfair.co.uk)
|
|
98
|
+
|| e.contains("address is not handled")
|
|
99
|
+
// permanent: 5.1.1 recipient is not exist (on @sim.com)
|
|
100
|
+
|| e.contains("recipient is not exist")
|
|
101
|
+
// permanent: 5.1.1 <EMAIL> Recipient not found. (on @4polymer.com)
|
|
102
|
+
|| e.contains("recipient not found")
|
|
103
|
+
// permanent: 5.7.1 Email doesn't exist. Please forward it or send it to contact@magency.f; 5.7.1 r - gcdp 38308e7fff4ca-30344241c74si13773751fa.513 - gsmtp"
|
|
104
|
+
|| e.contains("email doesn't exist")
|
|
105
|
+
// permanent: verify address failed, User not found ***@salemall.vn (on @salemall.vn)
|
|
106
|
+
|| e.contains("verify address failed")
|
|
107
|
+
// transient: unable to verify user (on @computan.net)
|
|
108
|
+
|| e.contains("unable to verify user")
|
|
109
|
+
// permanent: 5.1.1 Utilisateur inconnu (on @sante.fr)
|
|
110
|
+
|| e.contains("utilisateur inconnu")
|
|
111
|
+
// permanent: 5.1.1
|
|
112
|
+
|| e.contains("permanent: 5.1.1")
|
|
113
|
+
// permanent: 5.7.1
|
|
114
|
+
|| e.contains("permanent: 5.7.1")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Check that the mailbox has a full inbox.
|
|
118
|
+
pub fn is_full_inbox(e: &str) -> bool {
|
|
119
|
+
e.contains("insufficient")
|
|
120
|
+
// https://answers.microsoft.com/en-us/outlook_com/forum/all/how-do-i-interpret-the-delivery-failure-message/2f1bf9c0-8b03-4f8f-aacc-5f6ba60a73f3
|
|
121
|
+
|| e.contains("mailbox full")
|
|
122
|
+
// https://answers.microsoft.com/en-us/outlook_com/forum/all/how-do-i-interpret-the-delivery-failure-message/2f1bf9c0-8b03-4f8f-aacc-5f6ba60a73f3
|
|
123
|
+
|| e.contains("quote exceeded")
|
|
124
|
+
|| e.contains("over quota")
|
|
125
|
+
// 550 user has too many messages on the server
|
|
126
|
+
|| e.contains("too many messages")
|
|
127
|
+
// transient: 4.2.2 The recipient's inbox is out of storage space. Please direct the; 4.2.2 recipient to; 4.2.2 https://support.google.com/mail/?p=OverQuotaTemp 41be03b00d2f7-801d5c00011si4287631a12.311 - gsmtp (on @gmail.com)
|
|
128
|
+
|| e.contains("out of storage space")
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Check if the email account has been disabled or blocked by the email
|
|
132
|
+
/// provider.
|
|
133
|
+
pub fn is_disabled_account(e: &str) -> bool {
|
|
134
|
+
// 554 The email account that you tried to reach is disabled. Learn more at https://support.google.com/mail/?p=DisabledUser"
|
|
135
|
+
e.contains("disabled")
|
|
136
|
+
// 554 delivery error: Sorry your message to <EMAIL> cannot be delivered. This account has been disabled or discontinued
|
|
137
|
+
|| e.contains("discontinued")
|
|
138
|
+
//550 5.2.1 RACT MY.IP: Mailbox is inactive: <USER@hanmail.net><CRLF> (on hanmail.net)
|
|
139
|
+
|| e.contains("inactive")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Check if the error is an IO "incomplete" error.
|
|
143
|
+
pub fn is_err_io_errors(e: &SmtpError) -> bool {
|
|
144
|
+
match e {
|
|
145
|
+
SmtpError::AsyncSmtpError(AsyncSmtpError::Io(err)) => err.to_string() == "incomplete",
|
|
146
|
+
_ => false,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Check if the IP is blacklisted.
|
|
151
|
+
pub fn is_err_ip_blacklisted(e: &SmtpError) -> bool {
|
|
152
|
+
let e = match e {
|
|
153
|
+
SmtpError::AsyncSmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => {
|
|
154
|
+
// TODO We can use .to_string() after:
|
|
155
|
+
// https://github.com/async-email/async-smtp/pull/53
|
|
156
|
+
r.message.join("; ").to_lowercase()
|
|
157
|
+
}
|
|
158
|
+
_ => {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Permanent errors
|
|
164
|
+
|
|
165
|
+
// 5.7.1 IP address blacklisted by recipient
|
|
166
|
+
// 5.7.1 Service unavailable; Client host [147.75.45.223] is blacklisted. Visit https://www.sophos.com/en-us/threat-center/ip-lookup.aspx?ip=147.75.45.223 to request delisting
|
|
167
|
+
// 5.3.0 <EMAIL>... Mail from 147.75.45.223 rejected by Abusix blacklist (on @helsinki.fi)
|
|
168
|
+
e.contains("blacklist")
|
|
169
|
+
// Rejected because 23.129.64.213 is in a black list at b.barracudacentral.org
|
|
170
|
+
|| e.contains("black list")
|
|
171
|
+
// 5.7.1 Recipient not authorized, your IP has been found on a block list
|
|
172
|
+
// gmx.net (mxgmx117) Nemesis ESMTP Service not available; No SMTP service; IP address is block listed.; For explanation visit https://www.gmx.net/mail/senderguidelines?c=bl (on @gmx.net, @web.de)
|
|
173
|
+
|| e.contains("block list")
|
|
174
|
+
// Unable to add <EMAIL> because host 23.129.64.184 is listed on zen.spamhaus.org
|
|
175
|
+
// 5.7.1 Service unavailable, Client host [23.129.64.184] blocked using Spamhaus.
|
|
176
|
+
// 5.7.1 Email cannot be delivered. Reason: Email detected as Spam by spam filters.
|
|
177
|
+
|| e.contains("spam")
|
|
178
|
+
// host 23.129.64.216 is listed at combined.mail.abusix.zone (127.0.0.12,
|
|
179
|
+
|| e.contains("abusix")
|
|
180
|
+
// 5.7.1 Relaying denied. IP name possibly forged [45.154.35.252]
|
|
181
|
+
// 5.7.1 Relaying denied: You must check for new mail before sending mail. [23.129.64.216]
|
|
182
|
+
|| e.contains("relaying denied")
|
|
183
|
+
// 5.7.1 <unknown[23.129.64.100]>: Client host rejected: Access denied
|
|
184
|
+
|| e.contains("access denied")
|
|
185
|
+
// sorry, mail from your location [5.79.109.48] is administratively denied (#5.7.1)
|
|
186
|
+
|| e.contains("administratively denied")
|
|
187
|
+
// 5.7.606 Access denied, banned sending IP [23.129.64.216]
|
|
188
|
+
|| e.contains("banned")
|
|
189
|
+
// Blocked - see https://ipcheck.proofpoint.com/?ip=23.129.64.192
|
|
190
|
+
// 5.7.1 Mail from 23.129.64.183 has been blocked by Trend Micro Email Reputation Service.
|
|
191
|
+
|| e.contains("blocked")
|
|
192
|
+
// Connection rejected by policy [7.3] 38206, please visit https://support.symantec.com/en_US/article.TECH246726.html for more details about this error message.
|
|
193
|
+
|| e.contains("connection rejected")
|
|
194
|
+
// csi.mimecast.org Poor Reputation Sender. - https://community.mimecast.com/docs/DOC-1369#550 [6ATVl4DjOvSA6XNsWGoUFw.us31]
|
|
195
|
+
// Your access to this mail system has been rejected due to the sending MTA\'s poor reputation. If you believe that this failure is in error, please contact the intended recipient via alternate means.
|
|
196
|
+
|| e.contains("poor reputation")
|
|
197
|
+
// JunkMail rejected - (gmail.com) [193.218.118.140]:46615 is in an RBL: http://www.barracudanetworks.com/reputation/?pr=1&ip=193.218.118.140
|
|
198
|
+
|| e.contains("junkmail")
|
|
199
|
+
// mailfi01.lmco.com ESMTP 550 5.7.0 Mail from 18.234.87.196 refused by Proofpoint Reputation Services. SENDER please see and take action: https://support.proofpoint.com/dnsbl-lookup.cgi?18.234.87.196" (on @lmco.com)
|
|
200
|
+
|| e.contains("refused by proofpoint")
|
|
201
|
+
// resimta-h1p-037598.sys.comcast.net resimta-h1p-037598.sys.comcast.net 5.135.185.166 found on one or more DNSBLs, see http://postmaster.comcast.net/smtp-error-codes.php#BL000001 (on @comcast.net)
|
|
202
|
+
|| e.contains("dnsbl")
|
|
203
|
+
// smtp-fw-9107.amazon.com; SBRS score too low: http://www.senderbase.org/ (on @amazon.com)
|
|
204
|
+
|| e.contains("sbrs score too low")
|
|
205
|
+
// https://www.spamhaus.org/sbl/query/SBLCSShttps://www.spamhaus.org/query/ip/3.238.201.74 (on @knollridges.com.ph)
|
|
206
|
+
|| e.contains("spamhaus")
|
|
207
|
+
|
|
208
|
+
// Transient errors
|
|
209
|
+
|
|
210
|
+
// Blocked - see https://www.spamcop.net/bl.shtml?23.129.64.211
|
|
211
|
+
|| e.contains("blocked")
|
|
212
|
+
// 4.7.1 <EMAIL>: Relay access denied
|
|
213
|
+
|| e.contains("access denied")
|
|
214
|
+
// relay not permitted!
|
|
215
|
+
|| e.contains("relay not permitted")
|
|
216
|
+
// 23.129.64.216 is not yet authorized to deliver mail from
|
|
217
|
+
|| e.contains("not yet authorized")
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/// Check if the IP needs a reverse DNS.
|
|
221
|
+
pub fn is_err_needs_rdns(e: &SmtpError) -> bool {
|
|
222
|
+
let e = match e {
|
|
223
|
+
SmtpError::AsyncSmtpError(AsyncSmtpError::Transient(r) | AsyncSmtpError::Permanent(r)) => {
|
|
224
|
+
// TODO We can use .to_string() after:
|
|
225
|
+
// https://github.com/async-email/async-smtp/pull/53
|
|
226
|
+
r.message.join("; ").to_lowercase()
|
|
227
|
+
}
|
|
228
|
+
_ => {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// 4.7.25 Client host rejected: cannot find your hostname, [147.75.45.223]
|
|
234
|
+
// 4.7.1 Client host rejected: cannot find your reverse hostname, [147.75.45.223]
|
|
235
|
+
// 5.7.1 Client host rejected: cannot find your reverse hostname, [23.129.64.184]
|
|
236
|
+
e.contains("cannot find your reverse hostname")
|
|
237
|
+
// You dont seem to have a reverse dns entry. Come back later. You are greylisted for 20 minutes. See http://www.fsf.org/about/systems/greylisting
|
|
238
|
+
|| e.contains("reverse dns entry")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[cfg(test)]
|
|
242
|
+
mod tests {
|
|
243
|
+
|
|
244
|
+
use super::{is_err_ip_blacklisted, is_invalid};
|
|
245
|
+
use crate::EmailAddress;
|
|
246
|
+
use crate::SmtpError::AsyncSmtpError;
|
|
247
|
+
use async_smtp::{
|
|
248
|
+
error::Error,
|
|
249
|
+
response::{Category, Code, Detail, Response, Severity},
|
|
250
|
+
};
|
|
251
|
+
use std::str::FromStr;
|
|
252
|
+
|
|
253
|
+
#[test]
|
|
254
|
+
fn test_is_invalid() {
|
|
255
|
+
let email = EmailAddress::from_str("foo@bar.baz").unwrap();
|
|
256
|
+
|
|
257
|
+
assert!(!is_invalid(
|
|
258
|
+
"554 5.7.1 <mta.voipdir.net[]>: Client host rejected: Access denied",
|
|
259
|
+
&email
|
|
260
|
+
));
|
|
261
|
+
|
|
262
|
+
assert!(is_invalid(
|
|
263
|
+
"RCPT (***@stigpods.com.cn) dosn't exist",
|
|
264
|
+
&email
|
|
265
|
+
));
|
|
266
|
+
|
|
267
|
+
assert!(is_invalid(
|
|
268
|
+
"permanent: 5.1.1 MXIN501 mailbox foo@bar.baz unknown (on @virginmedia.com)",
|
|
269
|
+
&email
|
|
270
|
+
));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#[test]
|
|
274
|
+
fn test_is_err_ip_blacklisted() {
|
|
275
|
+
let err = Error::Permanent(Response::new(
|
|
276
|
+
Code::new(
|
|
277
|
+
Severity::PermanentNegativeCompletion,
|
|
278
|
+
Category::Information,
|
|
279
|
+
Detail::Zero,
|
|
280
|
+
),
|
|
281
|
+
vec![
|
|
282
|
+
"gmx.net (mxgmx117) Nemesis ESMTP Service not available".to_string(),
|
|
283
|
+
"No SMTP service".to_string(),
|
|
284
|
+
"IP address is block listed.".to_string(),
|
|
285
|
+
"For explanation visit https://www.gmx.net/mail/senderguidelines?c=bl".to_string(),
|
|
286
|
+
],
|
|
287
|
+
));
|
|
288
|
+
|
|
289
|
+
assert!(is_err_ip_blacklisted(&AsyncSmtpError(err)))
|
|
290
|
+
}
|
|
291
|
+
}
|