mirror of
https://github.com/chatmail/core.git
synced 2026-04-24 17:06:28 +03:00
feat: do not resolve MX records during configuration
MX record lookup was only used to detect Google Workspace domains. They can still be configured manually. We anyway do not want to encourage creating new profiles with Google Workspace as we don't have Gmail OAUTH2 token anymore and new users can more easily onboard with a chatmail relay.
This commit is contained in:
@@ -300,8 +300,6 @@ async fn get_configured_param(
|
||||
param.smtp.password.clone()
|
||||
};
|
||||
|
||||
let proxy_enabled = ctx.get_config_bool(Config::ProxyEnabled).await?;
|
||||
|
||||
let mut addr = param.addr.clone();
|
||||
if param.oauth2 {
|
||||
// the used oauth2 addr may differ, check this.
|
||||
@@ -343,7 +341,7 @@ async fn get_configured_param(
|
||||
"checking internal provider-info for offline autoconfig"
|
||||
);
|
||||
|
||||
provider = provider::get_provider_info(ctx, ¶m_domain, proxy_enabled).await;
|
||||
provider = provider::get_provider_info(¶m_domain);
|
||||
if let Some(provider) = provider {
|
||||
if provider.server.is_empty() {
|
||||
info!(ctx, "Offline autoconfig found, but no servers defined.");
|
||||
|
||||
@@ -53,7 +53,7 @@ pub async fn get_oauth2_url(
|
||||
addr: &str,
|
||||
redirect_uri: &str,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
context
|
||||
.sql
|
||||
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
|
||||
@@ -73,7 +73,7 @@ pub(crate) async fn get_oauth2_access_token(
|
||||
code: &str,
|
||||
regenerate: bool,
|
||||
) -> Result<Option<String>> {
|
||||
if let Some(oauth2) = Oauth2::from_address(context, addr).await {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_mutex.lock().await;
|
||||
|
||||
// read generated token
|
||||
@@ -221,7 +221,7 @@ pub(crate) async fn get_oauth2_addr(
|
||||
addr: &str,
|
||||
code: &str,
|
||||
) -> Result<Option<String>> {
|
||||
let oauth2 = match Oauth2::from_address(context, addr).await {
|
||||
let oauth2 = match Oauth2::from_address(addr) {
|
||||
Some(o) => o,
|
||||
None => return Ok(None),
|
||||
};
|
||||
@@ -256,15 +256,13 @@ pub(crate) async fn get_oauth2_addr(
|
||||
}
|
||||
|
||||
impl Oauth2 {
|
||||
async fn from_address(context: &Context, addr: &str) -> Option<Self> {
|
||||
fn from_address(addr: &str) -> Option<Self> {
|
||||
let addr_normalized = normalize_addr(addr);
|
||||
let skip_mx = true;
|
||||
if let Some(domain) = addr_normalized
|
||||
.find('@')
|
||||
.map(|index| addr_normalized.split_at(index + 1).1)
|
||||
{
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(context, domain, skip_mx)
|
||||
.await
|
||||
if let Some(oauth2_authorizer) = provider::get_provider_info(domain)
|
||||
.and_then(|provider| provider.oauth2_authorizer.as_ref())
|
||||
{
|
||||
return Some(match oauth2_authorizer {
|
||||
@@ -354,21 +352,16 @@ mod tests {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_oauth_from_address() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Delta Chat does not have working Gmail client ID anymore.
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@gmail.com").await, None);
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@googlemail.com").await, None);
|
||||
assert_eq!(Oauth2::from_address("hello@gmail.com"), None);
|
||||
assert_eq!(Oauth2::from_address("hello@googlemail.com"), None);
|
||||
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.com").await,
|
||||
Oauth2::from_address("hello@yandex.com"),
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(
|
||||
Oauth2::from_address(&t, "hello@yandex.ru").await,
|
||||
Some(OAUTH2_YANDEX)
|
||||
);
|
||||
assert_eq!(Oauth2::from_address(&t, "hello@web.de").await, None);
|
||||
assert_eq!(Oauth2::from_address("hello@yandex.ru"), Some(OAUTH2_YANDEX));
|
||||
assert_eq!(Oauth2::from_address("hello@web.de"), None);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
|
||||
149
src/provider.rs
149
src/provider.rs
@@ -4,13 +4,9 @@ pub(crate) mod data;
|
||||
|
||||
use anyhow::Result;
|
||||
use deltachat_contact_tools::EmailAddress;
|
||||
use hickory_resolver::name_server::TokioConnectionProvider;
|
||||
use hickory_resolver::{Resolver, TokioResolver, config};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::log::warn;
|
||||
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS};
|
||||
|
||||
/// Provider status according to manual testing.
|
||||
@@ -171,61 +167,17 @@ impl ProviderOptions {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get resolver to query MX records.
|
||||
///
|
||||
/// We first try to read the system's resolver from `/etc/resolv.conf`.
|
||||
/// This does not work at least on some Androids, therefore we fallback
|
||||
/// to the default `ResolverConfig` which uses eg. to google's `8.8.8.8` or `8.8.4.4`.
|
||||
fn get_resolver() -> Result<TokioResolver> {
|
||||
if let Ok(resolver) = TokioResolver::builder_tokio() {
|
||||
return Ok(resolver.build());
|
||||
}
|
||||
let resolver = Resolver::builder_with_config(
|
||||
config::ResolverConfig::default(),
|
||||
TokioConnectionProvider::default(),
|
||||
);
|
||||
Ok(resolver.build())
|
||||
}
|
||||
|
||||
/// Returns provider for the given an e-mail address.
|
||||
///
|
||||
/// Returns an error if provided address is not valid.
|
||||
pub async fn get_provider_info_by_addr(
|
||||
context: &Context,
|
||||
addr: &str,
|
||||
skip_mx: bool,
|
||||
) -> Result<Option<&'static Provider>> {
|
||||
pub fn get_provider_info_by_addr(addr: &str) -> Result<Option<&'static Provider>> {
|
||||
let addr = EmailAddress::new(addr)?;
|
||||
|
||||
let provider = get_provider_info(context, &addr.domain, skip_mx).await;
|
||||
Ok(provider)
|
||||
}
|
||||
|
||||
/// Returns provider for the given domain.
|
||||
///
|
||||
/// This function looks up domain in offline database first. If not
|
||||
/// found, it queries MX record for the domain and looks up offline
|
||||
/// database for MX domains.
|
||||
pub async fn get_provider_info(
|
||||
context: &Context,
|
||||
domain: &str,
|
||||
skip_mx: bool,
|
||||
) -> Option<&'static Provider> {
|
||||
if let Some(provider) = get_provider_by_domain(domain) {
|
||||
return Some(provider);
|
||||
}
|
||||
|
||||
if !skip_mx {
|
||||
if let Some(provider) = get_provider_by_mx(context, domain).await {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
Ok(get_provider_info(&addr.domain))
|
||||
}
|
||||
|
||||
/// Finds a provider in offline database based on domain.
|
||||
pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
pub fn get_provider_info(domain: &str) -> Option<&'static Provider> {
|
||||
let domain = domain.to_lowercase();
|
||||
for (pattern, provider) in PROVIDER_DATA {
|
||||
if let Some(suffix) = pattern.strip_prefix('*') {
|
||||
@@ -243,51 +195,6 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Finds a provider based on MX record for the given domain.
|
||||
///
|
||||
/// For security reasons, only Gmail can be configured this way.
|
||||
pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'static Provider> {
|
||||
let Ok(resolver) = get_resolver() else {
|
||||
warn!(context, "Cannot get a resolver to check MX records.");
|
||||
return None;
|
||||
};
|
||||
|
||||
let mut fqdn: String = domain.to_string();
|
||||
if !fqdn.ends_with('.') {
|
||||
fqdn.push('.');
|
||||
}
|
||||
|
||||
let Ok(mx_domains) = resolver.mx_lookup(fqdn).await else {
|
||||
warn!(context, "Cannot resolve MX records for {domain:?}.");
|
||||
return None;
|
||||
};
|
||||
|
||||
for (provider_domain_pattern, provider) in PROVIDER_DATA {
|
||||
if provider.id != "gmail" {
|
||||
// MX lookup is limited to Gmail for security reasons
|
||||
continue;
|
||||
}
|
||||
|
||||
if provider_domain_pattern.starts_with('*') {
|
||||
// Skip wildcard patterns.
|
||||
continue;
|
||||
}
|
||||
|
||||
let provider_fqdn = provider_domain_pattern.to_string() + ".";
|
||||
let provider_fqdn_dot = ".".to_string() + &provider_fqdn;
|
||||
|
||||
for mx_domain in mx_domains.iter() {
|
||||
let mx_domain = mx_domain.exchange().to_lowercase().to_utf8();
|
||||
|
||||
if mx_domain == provider_fqdn || mx_domain.ends_with(&provider_fqdn_dot) {
|
||||
return Some(provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a provider with the given ID from the database.
|
||||
pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
|
||||
if let Some(provider) = PROVIDER_IDS.get(id) {
|
||||
@@ -300,24 +207,23 @@ pub fn get_provider_by_id(id: &str) -> Option<&'static Provider> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test_utils::TestContext;
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain_unexistant() {
|
||||
let provider = get_provider_by_domain("unexistant.org");
|
||||
let provider = get_provider_info("unexistant.org");
|
||||
assert!(provider.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain_mixed_case() {
|
||||
let provider = get_provider_by_domain("nAUta.Cu").unwrap();
|
||||
let provider = get_provider_info("nAUta.Cu").unwrap();
|
||||
assert!(provider.status == Status::Ok);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_provider_by_domain() {
|
||||
fn test_get_provider_info() {
|
||||
let addr = "nauta.cu";
|
||||
let provider = get_provider_by_domain(addr).unwrap();
|
||||
let provider = get_provider_info(addr).unwrap();
|
||||
assert!(provider.status == Status::Ok);
|
||||
let server = &provider.server[0];
|
||||
assert_eq!(server.protocol, Protocol::Imap);
|
||||
@@ -332,13 +238,17 @@ mod tests {
|
||||
assert_eq!(server.port, 25);
|
||||
assert_eq!(server.username_pattern, UsernamePattern::Email);
|
||||
|
||||
let provider = get_provider_by_domain("gmail.com").unwrap();
|
||||
let provider = get_provider_info("gmail.com").unwrap();
|
||||
assert!(provider.status == Status::Preparation);
|
||||
assert!(!provider.before_login_hint.is_empty());
|
||||
assert!(!provider.overview_page.is_empty());
|
||||
|
||||
let provider = get_provider_by_domain("googlemail.com").unwrap();
|
||||
let provider = get_provider_info("googlemail.com").unwrap();
|
||||
assert!(provider.status == Status::Preparation);
|
||||
|
||||
assert!(get_provider_info("").is_none());
|
||||
assert!(get_provider_info("google.com").unwrap().id == "gmail");
|
||||
assert!(get_provider_info("example@google.com").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -347,39 +257,10 @@ mod tests {
|
||||
assert!(provider.id == "gmail");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_provider_info() {
|
||||
let t = TestContext::new().await;
|
||||
assert!(get_provider_info(&t, "", false).await.is_none());
|
||||
assert!(get_provider_info(&t, "google.com", false).await.unwrap().id == "gmail");
|
||||
assert!(
|
||||
get_provider_info(&t, "example@google.com", false)
|
||||
.await
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_get_provider_info_by_addr() -> Result<()> {
|
||||
let t = TestContext::new().await;
|
||||
assert!(
|
||||
get_provider_info_by_addr(&t, "google.com", false)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(
|
||||
get_provider_info_by_addr(&t, "example@google.com", false)
|
||||
.await?
|
||||
.unwrap()
|
||||
.id
|
||||
== "gmail"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_resolver() -> Result<()> {
|
||||
assert!(get_resolver().is_ok());
|
||||
assert!(get_provider_info_by_addr("google.com").is_err());
|
||||
assert!(get_provider_info_by_addr("example@google.com")?.unwrap().id == "gmail");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use crate::key::DcKey;
|
||||
use crate::log::{info, warn};
|
||||
use crate::login_param::ConfiguredLoginParam;
|
||||
use crate::message::MsgId;
|
||||
use crate::provider::get_provider_by_domain;
|
||||
use crate::provider::get_provider_info;
|
||||
use crate::sql::Sql;
|
||||
use crate::tools::{Time, inc_and_check, time_elapsed};
|
||||
|
||||
@@ -382,7 +382,7 @@ UPDATE chats SET protected=1, type=120 WHERE type=130;"#,
|
||||
context
|
||||
.set_config_internal(
|
||||
Config::ConfiguredProvider,
|
||||
get_provider_by_domain(&domain).map(|provider| provider.id),
|
||||
get_provider_info(&domain).map(|provider| provider.id),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user