1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// check-if-email-exists
// Copyright (C) 2018-2019 Amaury Martiny

// check-if-email-exists is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// check-if-email-exists is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with check-if-email-exists.  If not, see <http://www.gnu.org/licenses/>.

extern crate lettre;
#[macro_use]
extern crate log;
extern crate mailchecker;
extern crate native_tls;
extern crate rand;
extern crate rayon;
extern crate serde;
extern crate trust_dns_resolver;

mod mx;
mod smtp;
mod syntax;
mod util;

use lettre::{smtp::SMTP_PORT, EmailAddress};
use mx::{get_mx_lookup, MxDetails, MxError};
use rayon::prelude::*;
use serde::{ser::SerializeMap, Serialize, Serializer};
use smtp::{SmtpDetails, SmtpError};
use std::str::FromStr;
use syntax::{address_syntax, SyntaxDetails, SyntaxError};

/// All details about email address, MX records and SMTP responses
#[derive(Debug)]
pub struct SingleEmail {
	/// Details about the MX host
	pub mx: Result<MxDetails, MxError>,
	/// Details about the SMTP responses of the email
	pub smtp: Result<SmtpDetails, SmtpError>, // TODO Better Err type
	/// Details about the email address
	pub syntax: Result<SyntaxDetails, SyntaxError>,
}

// Implement a custom serialize
impl Serialize for SingleEmail {
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		// This is just used internally to get the nested error field
		#[derive(Serialize)]
		struct MyError<E> {
			error: E,
		}

		let mut map = serializer.serialize_map(Some(1))?;
		match &self.mx {
			Ok(t) => map.serialize_entry("mx", &t)?,
			Err(error) => map.serialize_entry("mx", &MyError { error })?,
		}
		match &self.smtp {
			Ok(t) => map.serialize_entry("smtp", &t)?,
			Err(error) => map.serialize_entry("smtp", &MyError { error })?,
		}
		match &self.syntax {
			Ok(t) => map.serialize_entry("syntax", &t)?,
			Err(error) => map.serialize_entry("syntax", &MyError { error })?,
		}
		map.end()
	}
}

/// The main function: checks email format, checks MX records, and checks SMTP
/// responses to the email inbox.
pub fn email_exists(to_email: &str, from_email: &str) -> SingleEmail {
	let from_email = EmailAddress::from_str(from_email).unwrap_or(
		EmailAddress::from_str("user@example.org").expect("This is a valid email. qed."),
	);

	debug!("Checking email '{}'", to_email);
	let my_syntax = match address_syntax(to_email) {
		Ok(s) => s,
		e => {
			return SingleEmail {
				mx: Err(MxError::Skipped),
				smtp: Err(SmtpError::Skipped),
				syntax: e,
			}
		}
	};
	debug!("Details of the email address: {:?}", my_syntax);

	debug!("Getting MX lookup...");
	let my_mx = match get_mx_lookup(&my_syntax) {
		Ok(m) => m,
		e => {
			return SingleEmail {
				mx: e,
				smtp: Err(SmtpError::Skipped),
				syntax: Ok(my_syntax),
			}
		}
	};
	debug!("Found the following MX hosts {:?}", my_mx);

	// `(host, port)` combination
	// We could add ports 465 and 587 too
	let combinations = my_mx
		.lookup
		.iter()
		.map(|host| (host.exchange(), SMTP_PORT))
		.collect::<Vec<_>>();

	let my_smtp = combinations
		// Concurrently find any combination that returns true for email_exists
		.par_iter()
		// Attempt to make a SMTP call to host
		.flat_map(|(host, port)| {
			smtp::smtp_details(
				&from_email,
				&my_syntax.address,
				host,
				*port,
				my_syntax.domain.as_str(),
			)
		})
		.find_any(|_| true)
		// If all smtp calls timed out/got refused/errored, we assume that the
		// ISP is blocking relevant ports
		.ok_or(SmtpError::BlockedByIsp);

	SingleEmail {
		mx: Ok(my_mx),
		smtp: my_smtp,
		syntax: Ok(my_syntax),
	}
}