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,531 @@
|
|
|
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
|
+
//! TODO: This will ultimately be moved to core.
|
|
18
|
+
|
|
19
|
+
use std::{collections::HashMap, time::Duration};
|
|
20
|
+
|
|
21
|
+
use crate::{
|
|
22
|
+
mx::{is_mimecast, is_proofpoint},
|
|
23
|
+
util::input_output::CheckEmailInputProxy,
|
|
24
|
+
};
|
|
25
|
+
use serde::{Deserialize, Serialize};
|
|
26
|
+
|
|
27
|
+
use super::{is_gmail, is_hotmail_b2b, is_hotmail_b2c, is_yahoo};
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, thiserror::Error)]
|
|
30
|
+
pub enum VerifMethodError {
|
|
31
|
+
#[error("Invalid proxies: {0}")]
|
|
32
|
+
InvalidProxies(String),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Reacher categorizes each email into one of the following email providers.
|
|
36
|
+
/// This is used to determine the verification method to use for each email
|
|
37
|
+
/// provider.
|
|
38
|
+
pub enum EmailProvider {
|
|
39
|
+
Gmail,
|
|
40
|
+
HotmailB2B,
|
|
41
|
+
HotmailB2C,
|
|
42
|
+
Proofpoint,
|
|
43
|
+
Mimecast,
|
|
44
|
+
Yahoo,
|
|
45
|
+
EverythingElse,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl EmailProvider {
|
|
49
|
+
/// Determine the email provider from the MX host.
|
|
50
|
+
pub fn from_mx_host(host: &str) -> Self {
|
|
51
|
+
if is_hotmail_b2c(host) {
|
|
52
|
+
EmailProvider::HotmailB2C
|
|
53
|
+
} else if is_hotmail_b2b(host) {
|
|
54
|
+
EmailProvider::HotmailB2B
|
|
55
|
+
} else if is_gmail(host) {
|
|
56
|
+
EmailProvider::Gmail
|
|
57
|
+
} else if is_yahoo(host) {
|
|
58
|
+
EmailProvider::Yahoo
|
|
59
|
+
} else if is_proofpoint(host) {
|
|
60
|
+
EmailProvider::Proofpoint
|
|
61
|
+
} else if is_mimecast(host) {
|
|
62
|
+
EmailProvider::Mimecast
|
|
63
|
+
} else {
|
|
64
|
+
EmailProvider::EverythingElse
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
type ProxyID = String;
|
|
70
|
+
// This is a convention ID for the proxy in the top-level "proxy" field of the
|
|
71
|
+
// request, which will be added as a key in the `proxies` map of the
|
|
72
|
+
// `VerifMethod` struct.
|
|
73
|
+
pub const DEFAULT_PROXY_ID: &str = "default";
|
|
74
|
+
|
|
75
|
+
/// The verification method to use for each email provider.
|
|
76
|
+
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Serialize)]
|
|
77
|
+
#[serde(default)]
|
|
78
|
+
pub struct VerifMethod {
|
|
79
|
+
/// Proxies to use for email verification. The key is any unique name for
|
|
80
|
+
/// the proxy, and the value is the proxy itself. For names, we recommend
|
|
81
|
+
/// "proxy1", "proxy2", etc.
|
|
82
|
+
pub proxies: HashMap<ProxyID, CheckEmailInputProxy>,
|
|
83
|
+
/// Verification method for Gmail.
|
|
84
|
+
pub gmail: GmailVerifMethod,
|
|
85
|
+
/// Verification method for Hotmail B2B.
|
|
86
|
+
pub hotmailb2b: HotmailB2BVerifMethod,
|
|
87
|
+
/// Verification method for Hotmail B2C.
|
|
88
|
+
pub hotmailb2c: HotmailB2CVerifMethod,
|
|
89
|
+
/// Verification method for Mimecast.
|
|
90
|
+
pub mimecast: MimecastVerifMethod,
|
|
91
|
+
/// Verification method for Proofpoint.
|
|
92
|
+
pub proofpoint: ProofpointVerifMethod,
|
|
93
|
+
/// Verification method for Yahoo.
|
|
94
|
+
pub yahoo: YahooVerifMethod,
|
|
95
|
+
/// Verification method for everything else.
|
|
96
|
+
pub everything_else: EverythingElseVerifMethod,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
impl VerifMethod {
|
|
100
|
+
/// Create a new `VerifMethod` with the same configuration for all email
|
|
101
|
+
/// providers.
|
|
102
|
+
pub fn new_with_same_config_for_all(
|
|
103
|
+
proxy: Option<CheckEmailInputProxy>,
|
|
104
|
+
hello_name: String,
|
|
105
|
+
from_email: String,
|
|
106
|
+
smtp_port: u16,
|
|
107
|
+
smtp_timeout: Option<Duration>,
|
|
108
|
+
retries: usize,
|
|
109
|
+
) -> Self {
|
|
110
|
+
let mut proxies = HashMap::new();
|
|
111
|
+
let proxy_id: Option<String> = if let Some(proxy) = proxy {
|
|
112
|
+
let proxy_id = DEFAULT_PROXY_ID.to_string();
|
|
113
|
+
proxies.insert(proxy_id.clone(), proxy);
|
|
114
|
+
Some(proxy_id)
|
|
115
|
+
} else {
|
|
116
|
+
None
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
Self {
|
|
120
|
+
proxies,
|
|
121
|
+
gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
122
|
+
proxy: proxy_id.clone(),
|
|
123
|
+
hello_name: hello_name.clone(),
|
|
124
|
+
from_email: from_email.clone(),
|
|
125
|
+
smtp_port,
|
|
126
|
+
smtp_timeout,
|
|
127
|
+
retries,
|
|
128
|
+
}),
|
|
129
|
+
hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
130
|
+
proxy: proxy_id.clone(),
|
|
131
|
+
hello_name: hello_name.clone(),
|
|
132
|
+
from_email: from_email.clone(),
|
|
133
|
+
smtp_port,
|
|
134
|
+
smtp_timeout,
|
|
135
|
+
retries,
|
|
136
|
+
}),
|
|
137
|
+
mimecast: MimecastVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
138
|
+
proxy: proxy_id.clone(),
|
|
139
|
+
hello_name: hello_name.clone(),
|
|
140
|
+
from_email: from_email.clone(),
|
|
141
|
+
smtp_port,
|
|
142
|
+
smtp_timeout,
|
|
143
|
+
retries,
|
|
144
|
+
}),
|
|
145
|
+
proofpoint: ProofpointVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
146
|
+
proxy: proxy_id.clone(),
|
|
147
|
+
hello_name: hello_name.clone(),
|
|
148
|
+
from_email: from_email.clone(),
|
|
149
|
+
smtp_port,
|
|
150
|
+
smtp_timeout,
|
|
151
|
+
retries,
|
|
152
|
+
}),
|
|
153
|
+
everything_else: EverythingElseVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
154
|
+
proxy: proxy_id,
|
|
155
|
+
hello_name,
|
|
156
|
+
from_email,
|
|
157
|
+
smtp_port,
|
|
158
|
+
smtp_timeout,
|
|
159
|
+
retries,
|
|
160
|
+
}),
|
|
161
|
+
..Default::default()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub fn validate_proxies(&self) -> Result<(), VerifMethodError> {
|
|
166
|
+
match &self.gmail {
|
|
167
|
+
GmailVerifMethod::Smtp(c) => {
|
|
168
|
+
if let Some(proxy_id) = &c.proxy {
|
|
169
|
+
self.proxies.get(proxy_id).ok_or_else(|| {
|
|
170
|
+
VerifMethodError::InvalidProxies(format!("Invalid Gmail proxy {proxy_id}"))
|
|
171
|
+
})?;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
match &self.hotmailb2b {
|
|
177
|
+
HotmailB2BVerifMethod::Smtp(c) => {
|
|
178
|
+
if let Some(proxy_id) = &c.proxy {
|
|
179
|
+
self.proxies.get(proxy_id).ok_or_else(|| {
|
|
180
|
+
VerifMethodError::InvalidProxies(format!(
|
|
181
|
+
"Invalid Hotmail B2B proxy {proxy_id}"
|
|
182
|
+
))
|
|
183
|
+
})?;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
if let HotmailB2CVerifMethod::Smtp(c) = &self.hotmailb2c {
|
|
189
|
+
if let Some(proxy_id) = &c.proxy {
|
|
190
|
+
self.proxies.get(proxy_id).ok_or_else(|| {
|
|
191
|
+
VerifMethodError::InvalidProxies(format!(
|
|
192
|
+
"Invalid Hotmail B2C proxy {proxy_id}"
|
|
193
|
+
))
|
|
194
|
+
})?;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
if let YahooVerifMethod::Smtp(c) = &self.yahoo {
|
|
199
|
+
if let Some(proxy_id) = &c.proxy {
|
|
200
|
+
self.proxies.get(proxy_id).ok_or_else(|| {
|
|
201
|
+
VerifMethodError::InvalidProxies(format!("Invalid Yahoo proxy {proxy_id}"))
|
|
202
|
+
})?;
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
match &self.everything_else {
|
|
207
|
+
EverythingElseVerifMethod::Smtp(c) => {
|
|
208
|
+
if let Some(proxy_id) = &c.proxy {
|
|
209
|
+
self.proxies.get(proxy_id).ok_or_else(|| {
|
|
210
|
+
VerifMethodError::InvalidProxies(format!(
|
|
211
|
+
"Invalid EverythingElse proxy {proxy_id}"
|
|
212
|
+
))
|
|
213
|
+
})?;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
Ok(())
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Get the proxy to use for the email provider. If there is a
|
|
222
|
+
/// configuration error, such as an unconfigured proxy, this will return
|
|
223
|
+
/// an error.
|
|
224
|
+
pub fn get_proxy(&self, email_provider: EmailProvider) -> Option<&CheckEmailInputProxy> {
|
|
225
|
+
match email_provider {
|
|
226
|
+
EmailProvider::Gmail => match &self.gmail {
|
|
227
|
+
GmailVerifMethod::Smtp(c) => c
|
|
228
|
+
.proxy
|
|
229
|
+
.as_ref()
|
|
230
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
231
|
+
},
|
|
232
|
+
EmailProvider::HotmailB2B => match &self.hotmailb2b {
|
|
233
|
+
HotmailB2BVerifMethod::Smtp(c) => c
|
|
234
|
+
.proxy
|
|
235
|
+
.as_ref()
|
|
236
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
237
|
+
},
|
|
238
|
+
EmailProvider::HotmailB2C => match &self.hotmailb2c {
|
|
239
|
+
HotmailB2CVerifMethod::Smtp(c) => c
|
|
240
|
+
.proxy
|
|
241
|
+
.as_ref()
|
|
242
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
243
|
+
_ => None,
|
|
244
|
+
},
|
|
245
|
+
EmailProvider::Mimecast => match &self.mimecast {
|
|
246
|
+
MimecastVerifMethod::Smtp(c) => c
|
|
247
|
+
.proxy
|
|
248
|
+
.as_ref()
|
|
249
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
250
|
+
},
|
|
251
|
+
EmailProvider::Proofpoint => match &self.proofpoint {
|
|
252
|
+
ProofpointVerifMethod::Smtp(c) => c
|
|
253
|
+
.proxy
|
|
254
|
+
.as_ref()
|
|
255
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
256
|
+
},
|
|
257
|
+
EmailProvider::Yahoo => match &self.yahoo {
|
|
258
|
+
YahooVerifMethod::Smtp(c) => c
|
|
259
|
+
.proxy
|
|
260
|
+
.as_ref()
|
|
261
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
262
|
+
_ => None,
|
|
263
|
+
},
|
|
264
|
+
EmailProvider::EverythingElse => match &self.everything_else {
|
|
265
|
+
EverythingElseVerifMethod::Smtp(c) => c
|
|
266
|
+
.proxy
|
|
267
|
+
.as_ref()
|
|
268
|
+
.and_then(|proxy_id| self.proxies.get(proxy_id)),
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
|
|
275
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
276
|
+
pub enum GmailVerifMethod {
|
|
277
|
+
Smtp(VerifMethodSmtpConfig),
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
impl Default for GmailVerifMethod {
|
|
281
|
+
fn default() -> Self {
|
|
282
|
+
GmailVerifMethod::Smtp(VerifMethodSmtpConfig::default())
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
|
|
287
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
288
|
+
pub enum HotmailB2BVerifMethod {
|
|
289
|
+
/// Use Hotmail's SMTP servers to check if an email exists.
|
|
290
|
+
Smtp(VerifMethodSmtpConfig),
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
impl Default for HotmailB2BVerifMethod {
|
|
294
|
+
fn default() -> Self {
|
|
295
|
+
HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig::default())
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
#[derive(Debug, Default, Deserialize, Clone, PartialEq, Serialize)]
|
|
300
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
301
|
+
pub enum HotmailB2CVerifMethod {
|
|
302
|
+
/// Use Hotmail's password recovery page to check if an email exists.
|
|
303
|
+
///
|
|
304
|
+
/// This assumes you have a WebDriver compatible process running, then pass
|
|
305
|
+
/// its endpoint, usually http://localhost:9515, into the environment
|
|
306
|
+
/// variable RCH_WEBDRIVER_ADDR. We recommend running chromedriver (and not
|
|
307
|
+
/// geckodriver) as it allows parallel requests.
|
|
308
|
+
#[default]
|
|
309
|
+
Headless,
|
|
310
|
+
/// Use Hotmail's SMTP servers to check if an email exists.
|
|
311
|
+
Smtp(VerifMethodSmtpConfig),
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
|
|
315
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
316
|
+
pub enum MimecastVerifMethod {
|
|
317
|
+
/// Use Mimecast's SMTP servers to check if an email exists.
|
|
318
|
+
Smtp(VerifMethodSmtpConfig),
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
impl Default for MimecastVerifMethod {
|
|
322
|
+
fn default() -> Self {
|
|
323
|
+
MimecastVerifMethod::Smtp(VerifMethodSmtpConfig::default())
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
|
|
328
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
329
|
+
pub enum ProofpointVerifMethod {
|
|
330
|
+
/// Use Proofpoint's SMTP servers to check if an email exists.
|
|
331
|
+
Smtp(VerifMethodSmtpConfig),
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
impl Default for ProofpointVerifMethod {
|
|
335
|
+
fn default() -> Self {
|
|
336
|
+
ProofpointVerifMethod::Smtp(VerifMethodSmtpConfig::default())
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[derive(Debug, Default, Deserialize, Clone, PartialEq, Serialize)]
|
|
341
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
342
|
+
pub enum YahooVerifMethod {
|
|
343
|
+
/// Use Yahoo's API to check if an email exists.
|
|
344
|
+
Api,
|
|
345
|
+
/// Use Yahoo's password recovery page to check if an email exists.
|
|
346
|
+
///
|
|
347
|
+
/// This assumes you have a WebDriver compatible process running, then pass
|
|
348
|
+
/// its endpoint, usually http://localhost:9515, into the environment
|
|
349
|
+
/// variable RCH_WEBDRIVER_ADDR. We recommend running chromedriver (and not
|
|
350
|
+
/// geckodriver) as it allows parallel requests.
|
|
351
|
+
#[default]
|
|
352
|
+
Headless,
|
|
353
|
+
/// Use Yahoo's SMTP servers to check if an email exists.
|
|
354
|
+
Smtp(VerifMethodSmtpConfig),
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
#[derive(Debug, Deserialize, Clone, PartialEq, Serialize)]
|
|
358
|
+
#[serde(tag = "type", rename_all = "lowercase")]
|
|
359
|
+
pub enum EverythingElseVerifMethod {
|
|
360
|
+
/// Use the SMTP server of the email provider to check if an email exists.
|
|
361
|
+
Smtp(VerifMethodSmtpConfig),
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
impl Default for EverythingElseVerifMethod {
|
|
365
|
+
fn default() -> Self {
|
|
366
|
+
EverythingElseVerifMethod::Smtp(VerifMethodSmtpConfig::default())
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/// Configuration on the SMTP verification method. If it used mostly as a
|
|
371
|
+
/// serializable struct, to be converted into the domain type
|
|
372
|
+
/// `VerifMethodSmtp`.
|
|
373
|
+
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
|
374
|
+
#[serde(default)]
|
|
375
|
+
pub struct VerifMethodSmtpConfig {
|
|
376
|
+
/// Email to use in the `MAIL FROM:` SMTP command.
|
|
377
|
+
///
|
|
378
|
+
/// Defaults to "reacher.email@gmail.com", which is an unused addressed
|
|
379
|
+
/// owned by Reacher.
|
|
380
|
+
///
|
|
381
|
+
pub from_email: String,
|
|
382
|
+
/// Name to use in the `EHLO:` SMTP command.
|
|
383
|
+
///
|
|
384
|
+
/// Defaults to "gmail.com" (note: "localhost" is not a FQDN), mostly for
|
|
385
|
+
/// testing purposes. You should set this to a domain you own.
|
|
386
|
+
pub hello_name: String,
|
|
387
|
+
/// Use a proxy to check if an email exists. This proxy must exist in the
|
|
388
|
+
/// `VerifMethod.proxies` field, and it must be a SOCKS5 proxy.
|
|
389
|
+
pub proxy: Option<ProxyID>,
|
|
390
|
+
/// SMTP port to use for email validation. Generally, ports 25, 465, 587
|
|
391
|
+
/// and 2525 are used.
|
|
392
|
+
///
|
|
393
|
+
/// Defaults to 25.
|
|
394
|
+
pub smtp_port: u16,
|
|
395
|
+
/// Add timeout for the SMTP verification step. Set to None if you don't
|
|
396
|
+
/// want to use a timeout. This timeout is per SMTP connection. For
|
|
397
|
+
/// instance, if you set the number of retries to 2, then the total time
|
|
398
|
+
/// for the SMTP verification step can be up to 2 * `smtp_timeout`.
|
|
399
|
+
///
|
|
400
|
+
/// Defaults to None.
|
|
401
|
+
pub smtp_timeout: Option<Duration>,
|
|
402
|
+
/// Number of total SMTP connections to do. Setting to 2 might bypass
|
|
403
|
+
/// greylisting on some servers, but takes more time.
|
|
404
|
+
///
|
|
405
|
+
/// This setting's naming is a bit misleading, as it's not really retries,
|
|
406
|
+
/// but the total number of SMTP connections to do.
|
|
407
|
+
///
|
|
408
|
+
/// Defaults to 1.
|
|
409
|
+
pub retries: usize,
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
impl Default for VerifMethodSmtpConfig {
|
|
413
|
+
fn default() -> Self {
|
|
414
|
+
Self {
|
|
415
|
+
from_email: "reacher@gmail.com".to_string(),
|
|
416
|
+
hello_name: "gmail.com".to_string(),
|
|
417
|
+
proxy: None,
|
|
418
|
+
smtp_port: 25,
|
|
419
|
+
smtp_timeout: None,
|
|
420
|
+
retries: 1,
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
#[derive(Debug, Deserialize, Serialize)]
|
|
426
|
+
pub struct VerifMethodSmtp {
|
|
427
|
+
pub config: VerifMethodSmtpConfig,
|
|
428
|
+
pub proxy: Option<CheckEmailInputProxy>,
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
impl VerifMethodSmtp {
|
|
432
|
+
pub fn new(config: VerifMethodSmtpConfig, proxy: Option<CheckEmailInputProxy>) -> Self {
|
|
433
|
+
Self { config, proxy }
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[cfg(test)]
|
|
438
|
+
mod tests {
|
|
439
|
+
use super::*;
|
|
440
|
+
|
|
441
|
+
#[test]
|
|
442
|
+
fn test_validate_proxies() {
|
|
443
|
+
let mut proxies = HashMap::new();
|
|
444
|
+
proxies.insert("proxy1".to_string(), CheckEmailInputProxy::default());
|
|
445
|
+
proxies.insert("proxy2".to_string(), CheckEmailInputProxy::default());
|
|
446
|
+
|
|
447
|
+
// Test invalid proxies.
|
|
448
|
+
let verif_method = VerifMethod {
|
|
449
|
+
proxies: proxies.clone(),
|
|
450
|
+
gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
451
|
+
proxy: Some("proxy3".to_string()),
|
|
452
|
+
..Default::default()
|
|
453
|
+
}),
|
|
454
|
+
..Default::default()
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
assert!(verif_method.validate_proxies().is_err());
|
|
458
|
+
|
|
459
|
+
// Test valid proxies.
|
|
460
|
+
let verif_method = VerifMethod {
|
|
461
|
+
proxies,
|
|
462
|
+
gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
463
|
+
proxy: Some("proxy1".to_string()),
|
|
464
|
+
..Default::default()
|
|
465
|
+
}),
|
|
466
|
+
hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
467
|
+
proxy: Some("proxy2".to_string()),
|
|
468
|
+
..Default::default()
|
|
469
|
+
}),
|
|
470
|
+
hotmailb2c: HotmailB2CVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
471
|
+
proxy: Some("proxy1".to_string()),
|
|
472
|
+
..Default::default()
|
|
473
|
+
}),
|
|
474
|
+
yahoo: YahooVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
475
|
+
proxy: Some("proxy2".to_string()),
|
|
476
|
+
..Default::default()
|
|
477
|
+
}),
|
|
478
|
+
..Default::default()
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
assert!(verif_method.validate_proxies().is_ok());
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
#[test]
|
|
485
|
+
fn test_get_proxy() {
|
|
486
|
+
let mut proxies = HashMap::new();
|
|
487
|
+
proxies.insert("proxy1".to_string(), CheckEmailInputProxy::default());
|
|
488
|
+
proxies.insert("proxy2".to_string(), CheckEmailInputProxy::default());
|
|
489
|
+
|
|
490
|
+
let verif_method = VerifMethod {
|
|
491
|
+
proxies: proxies.clone(),
|
|
492
|
+
gmail: GmailVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
493
|
+
proxy: Some("proxy1".to_string()),
|
|
494
|
+
..Default::default()
|
|
495
|
+
}),
|
|
496
|
+
hotmailb2b: HotmailB2BVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
497
|
+
proxy: Some("proxy2".to_string()),
|
|
498
|
+
..Default::default()
|
|
499
|
+
}),
|
|
500
|
+
hotmailb2c: HotmailB2CVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
501
|
+
proxy: Some("proxy1".to_string()),
|
|
502
|
+
..Default::default()
|
|
503
|
+
}),
|
|
504
|
+
yahoo: YahooVerifMethod::Smtp(VerifMethodSmtpConfig {
|
|
505
|
+
proxy: Some("proxy2".to_string()),
|
|
506
|
+
..Default::default()
|
|
507
|
+
}),
|
|
508
|
+
..Default::default()
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
assert_eq!(
|
|
512
|
+
verif_method.get_proxy(EmailProvider::Gmail),
|
|
513
|
+
proxies.get("proxy1")
|
|
514
|
+
);
|
|
515
|
+
assert_eq!(
|
|
516
|
+
verif_method.get_proxy(EmailProvider::HotmailB2B),
|
|
517
|
+
proxies.get("proxy2")
|
|
518
|
+
);
|
|
519
|
+
assert_eq!(
|
|
520
|
+
verif_method.get_proxy(EmailProvider::HotmailB2C),
|
|
521
|
+
proxies.get("proxy1")
|
|
522
|
+
);
|
|
523
|
+
assert_eq!(
|
|
524
|
+
verif_method.get_proxy(EmailProvider::Yahoo),
|
|
525
|
+
proxies.get("proxy2")
|
|
526
|
+
);
|
|
527
|
+
assert!(verif_method
|
|
528
|
+
.get_proxy(EmailProvider::EverythingElse)
|
|
529
|
+
.is_none());
|
|
530
|
+
}
|
|
531
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
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 super::YahooError;
|
|
18
|
+
use crate::LOG_TARGET;
|
|
19
|
+
use crate::{
|
|
20
|
+
smtp::{http_api::create_client, SmtpDetails},
|
|
21
|
+
util::input_output::CheckEmailInput,
|
|
22
|
+
};
|
|
23
|
+
use regex::Regex;
|
|
24
|
+
use serde::{Deserialize, Serialize};
|
|
25
|
+
|
|
26
|
+
const SIGNUP_PAGE: &str = "https://login.yahoo.com/account/create?specId=yidReg&lang=en-US&src=&done=https%3A%2F%2Fwww.yahoo.com&display=login";
|
|
27
|
+
const SIGNUP_API: &str = "https://login.yahoo.com/account/module/create?validateField=yid";
|
|
28
|
+
const USER_AGENT: &str = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36"; // Fake one to use in API requests
|
|
29
|
+
|
|
30
|
+
/// The form inputs to pass into the HTTP request.
|
|
31
|
+
#[derive(Serialize)]
|
|
32
|
+
struct FormRequest {
|
|
33
|
+
acrumb: String,
|
|
34
|
+
#[serde(rename(serialize = "sessionIndex"))]
|
|
35
|
+
session_index: String,
|
|
36
|
+
#[serde(rename(serialize = "specId"))]
|
|
37
|
+
spec_id: String,
|
|
38
|
+
#[serde(rename(serialize = "userId"))]
|
|
39
|
+
user_id: String,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
impl Default for FormRequest {
|
|
43
|
+
fn default() -> Self {
|
|
44
|
+
FormRequest {
|
|
45
|
+
acrumb: "".into(),
|
|
46
|
+
session_index: "".into(),
|
|
47
|
+
spec_id: "yidReg".into(),
|
|
48
|
+
user_id: "".into(),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl FormRequest {
|
|
54
|
+
fn new(acrumb: String, session_index: String, user_id: String) -> Self {
|
|
55
|
+
FormRequest {
|
|
56
|
+
acrumb,
|
|
57
|
+
session_index,
|
|
58
|
+
user_id,
|
|
59
|
+
..Default::default()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// One item in the response of the HTTP form request.
|
|
65
|
+
#[derive(Debug, Deserialize)]
|
|
66
|
+
struct FormResponseItem {
|
|
67
|
+
error: String,
|
|
68
|
+
name: String,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/// The response of the HTTP form request.
|
|
72
|
+
#[derive(Debug, Deserialize)]
|
|
73
|
+
struct FormResponse {
|
|
74
|
+
errors: Vec<FormResponseItem>,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Use well-crafted HTTP requests to verify if a Yahoo email address exists.
|
|
78
|
+
/// Inspired by https://github.com/hbattat/verifyEmail.
|
|
79
|
+
pub async fn check_api(to_email: &str, input: &CheckEmailInput) -> Result<SmtpDetails, YahooError> {
|
|
80
|
+
let res = create_client(input, "yahoo")?
|
|
81
|
+
.get(SIGNUP_PAGE)
|
|
82
|
+
.header("User-Agent", USER_AGENT)
|
|
83
|
+
.send()
|
|
84
|
+
.await?;
|
|
85
|
+
|
|
86
|
+
// Get the cookies from the response.
|
|
87
|
+
let cookies = match res.headers().get("Set-Cookie") {
|
|
88
|
+
Some(x) => x.to_owned(),
|
|
89
|
+
_ => {
|
|
90
|
+
return Err(YahooError::NoCookie);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
tracing::debug!(
|
|
95
|
+
target: LOG_TARGET,
|
|
96
|
+
email=to_email,
|
|
97
|
+
"Yahoo succesfully got cookies after response"
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
let body = res.text().await?;
|
|
101
|
+
|
|
102
|
+
let username = to_email
|
|
103
|
+
.split('@')
|
|
104
|
+
.next()
|
|
105
|
+
.expect("The email is well-formed. qed.");
|
|
106
|
+
|
|
107
|
+
// From the cookies, fetch the "acrumb" field.
|
|
108
|
+
let acrumb = match cookies.to_str() {
|
|
109
|
+
Ok(x) => x,
|
|
110
|
+
_ => {
|
|
111
|
+
return Err(YahooError::NoAcrumb);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
let re = Regex::new(r"s=(?P<acrumb>[^;]*)&d").expect("Correct regex. qed.");
|
|
115
|
+
let acrumb = match re.captures(acrumb) {
|
|
116
|
+
Some(x) => x,
|
|
117
|
+
_ => {
|
|
118
|
+
return Err(YahooError::NoAcrumb);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let re =
|
|
123
|
+
Regex::new(r#"<input type="hidden" value="(?P<sessionIndex>.*)" name="sessionIndex">"#)
|
|
124
|
+
.expect("Correct regex. qed");
|
|
125
|
+
let session_index = match re.captures(&body) {
|
|
126
|
+
Some(y) => y,
|
|
127
|
+
_ => {
|
|
128
|
+
return Err(YahooError::NoSessionIndex);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// Mimic a real HTTP request.
|
|
133
|
+
let res = create_client(input, "yahoo")?
|
|
134
|
+
.post(SIGNUP_API)
|
|
135
|
+
.header("Origin", "https://login.yahoo.com")
|
|
136
|
+
.header("X-Requested-With", "XMLHttpRequest")
|
|
137
|
+
.header("User-Agent", USER_AGENT)
|
|
138
|
+
.header(
|
|
139
|
+
"Content-type",
|
|
140
|
+
"application/x-www-form-urlencoded; charset=UTF-8",
|
|
141
|
+
)
|
|
142
|
+
.header("Accept", "*/*")
|
|
143
|
+
.header("Referer", SIGNUP_PAGE)
|
|
144
|
+
.header("Accept-Encoding", "gzip, deflate, br")
|
|
145
|
+
.header("Accept-Language", "en-US,en;q=0.8,ar;q=0.6")
|
|
146
|
+
.header("Cookie", &cookies)
|
|
147
|
+
.json(&FormRequest::new(
|
|
148
|
+
acrumb["acrumb"].to_string(),
|
|
149
|
+
session_index["sessionIndex"].to_string(),
|
|
150
|
+
username.into(),
|
|
151
|
+
))
|
|
152
|
+
.send()
|
|
153
|
+
.await?
|
|
154
|
+
.json::<FormResponse>()
|
|
155
|
+
.await?;
|
|
156
|
+
|
|
157
|
+
tracing::debug!(
|
|
158
|
+
target: LOG_TARGET,
|
|
159
|
+
email=to_email,
|
|
160
|
+
"Yahoo 2nd response: {:?}",
|
|
161
|
+
res
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
let username_exists = res.errors.iter().any(|item| {
|
|
165
|
+
item.name == "userId"
|
|
166
|
+
&& (item.error == "IDENTIFIER_NOT_AVAILABLE" || item.error == "IDENTIFIER_EXISTS")
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
Ok(SmtpDetails {
|
|
170
|
+
can_connect_smtp: true,
|
|
171
|
+
is_deliverable: username_exists,
|
|
172
|
+
..Default::default()
|
|
173
|
+
})
|
|
174
|
+
}
|