Compare commits

..

1 Commits

Author SHA1 Message Date
link2xt
bd81ecdb5d Add option to force E2EE encryption preference
Enabling this option ignores Autocrypt recommendation taking others
encryption preferences into account and overrides it with our own
encryption preference when possible.

This is similar to user always manually enabling/disabling encryption
manually in a classic Autocrypt-capable MUA UI whenever the control is
not disabled.

The goal is to allow encrypting responses to MUAs which can send
Autocrypt header but don't support setting encryption preference, such
as Thunderbird 91.
2021-11-28 16:32:18 +00:00
16 changed files with 125 additions and 269 deletions

View File

@@ -1,19 +1,5 @@
# Changelog
## 1.70.0
### Fixes
- fix: do not abort Param parsing on unknown keys #2856
- fix: execute `Chat-Group-Member-Removed:` even when arriving disordered #2857
## 1.69.0
### Fixes
- fix group-related system messages in multi-device setups #2848
- fix "Google Workspace" (former "G Suite") issues related to bad resolvers #2852
## 1.68.0
### Fixes

4
Cargo.lock generated
View File

@@ -1056,7 +1056,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.70.0"
version = "1.68.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1136,7 +1136,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.70.0"
version = "1.68.0"
dependencies = [
"anyhow",
"async-std",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.70.0"
version = "1.68.0"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"
license = "MPL-2.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.70.0"
version = "1.68.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -287,6 +287,8 @@ char* dc_get_blobdir (const dc_context_t* context);
* To save traffic, however, the avatar is attached only as needed
* and also recoded to a reasonable size.
* - `e2ee_enabled` = 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
* - `e2ee_force` = 1=ignore encryption preferences of others,
* 0=use majority vote when deciding whether to encrypt (default).
* - `mdns_enabled` = 0=do not send or request read receipts,
* 1=send and request read receipts (default)
* - `bcc_self` = 0=do not send a copy of outgoing messages to self (default),

View File

@@ -3753,11 +3753,7 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
match socks5_enabled {
Ok(socks5_enabled) => {
match block_on(provider::get_provider_info(
ctx,
addr.as_str(),
socks5_enabled,
)) {
match block_on(provider::get_provider_info(addr.as_str(), socks5_enabled)) {
Some(provider) => provider,
None => ptr::null_mut(),
}

View File

@@ -1185,7 +1185,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
let socks5_enabled = context
.get_config_bool(config::Config::Socks5Enabled)
.await?;
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
match provider::get_provider_info(arg1, socks5_enabled).await {
Some(info) => {
println!("Information for provider belonging to {}:", arg1);
println!("status: {}", info.status as u32);

View File

@@ -3225,147 +3225,6 @@ mod tests {
assert_eq!(added, false);
}
#[async_std::test]
async fn test_modify_chat_multi_device() -> Result<()> {
let a1 = TestContext::new_alice().await;
let a2 = TestContext::new_alice().await;
a1.set_config_bool(Config::BccSelf, true).await?;
// create group and sync it to the second device
let a1_chat_id = create_group_chat(&a1, ProtectionStatus::Unprotected, "foo").await?;
send_text_msg(&a1, a1_chat_id, "ho!".to_string()).await?;
let a1_msg = a1.get_last_msg().await;
let a1_chat = Chat::load_from_db(&a1, a1_chat_id).await?;
a2.recv_msg(&a1.pop_sent_msg().await).await;
let a2_msg = a2.get_last_msg().await;
let a2_chat_id = a2_msg.chat_id;
let a2_chat = Chat::load_from_db(&a2, a2_chat_id).await?;
assert!(!a1_msg.is_system_message());
assert!(!a2_msg.is_system_message());
assert_eq!(a1_chat.grpid, a2_chat.grpid);
assert_eq!(a1_chat.name, "foo");
assert_eq!(a2_chat.name, "foo");
assert_eq!(a1_chat.get_profile_image(&a1).await?, None);
assert_eq!(a2_chat.get_profile_image(&a2).await?, None);
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 1);
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 1);
// add a member to the group
let bob = Contact::create(&a1, "", "bob@example.org").await?;
add_contact_to_chat(&a1, a1_chat_id, bob).await?;
let a1_msg = a1.get_last_msg().await;
a2.recv_msg(&a1.pop_sent_msg().await).await;
let a2_msg = a2.get_last_msg().await;
assert!(a1_msg.is_system_message());
assert!(a2_msg.is_system_message());
assert_eq!(a1_msg.get_info_type(), SystemMessage::MemberAddedToGroup);
assert_eq!(a2_msg.get_info_type(), SystemMessage::MemberAddedToGroup);
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 2);
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 2);
// rename the group
set_chat_name(&a1, a1_chat_id, "bar").await?;
let a1_msg = a1.get_last_msg().await;
a2.recv_msg(&a1.pop_sent_msg().await).await;
let a2_msg = a2.get_last_msg().await;
assert!(a1_msg.is_system_message());
assert!(a2_msg.is_system_message());
assert_eq!(a1_msg.get_info_type(), SystemMessage::GroupNameChanged);
assert_eq!(a2_msg.get_info_type(), SystemMessage::GroupNameChanged);
assert_eq!(Chat::load_from_db(&a1, a1_chat_id).await?.name, "bar");
assert_eq!(Chat::load_from_db(&a2, a2_chat_id).await?.name, "bar");
// remove member from group
remove_contact_from_chat(&a1, a1_chat_id, bob).await?;
let a1_msg = a1.get_last_msg().await;
a2.recv_msg(&a1.pop_sent_msg().await).await;
let a2_msg = a2.get_last_msg().await;
assert!(a1_msg.is_system_message());
assert!(a2_msg.is_system_message());
assert_eq!(
a1_msg.get_info_type(),
SystemMessage::MemberRemovedFromGroup
);
assert_eq!(
a2_msg.get_info_type(),
SystemMessage::MemberRemovedFromGroup
);
assert_eq!(get_chat_contacts(&a1, a1_chat_id).await?.len(), 1);
assert_eq!(get_chat_contacts(&a2, a2_chat_id).await?.len(), 1);
Ok(())
}
#[async_std::test]
async fn test_modify_chat_disordered() -> Result<()> {
// Alice creates a group with Bob, Claire and Daisy and then removes Claire and Daisy
// (sleep() is needed as otherwise smeared time from Alice looks to Bob like messages from the future which are all set to "now" then)
let alice = TestContext::new_alice().await;
let bob_id = Contact::create(&alice, "", "bob@example.net").await?;
let claire_id = Contact::create(&alice, "", "claire@foo.de").await?;
let daisy_id = Contact::create(&alice, "", "daisy@bar.de").await?;
let alice_chat_id = create_group_chat(&alice, ProtectionStatus::Unprotected, "foo").await?;
send_text_msg(&alice, alice_chat_id, "populate".to_string()).await?;
add_contact_to_chat(&alice, alice_chat_id, bob_id).await?;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
let add1 = alice.pop_sent_msg().await;
add_contact_to_chat(&alice, alice_chat_id, claire_id).await?;
let add2 = alice.pop_sent_msg().await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
add_contact_to_chat(&alice, alice_chat_id, daisy_id).await?;
let add3 = alice.pop_sent_msg().await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 4);
remove_contact_from_chat(&alice, alice_chat_id, claire_id).await?;
let remove1 = alice.pop_sent_msg().await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
remove_contact_from_chat(&alice, alice_chat_id, daisy_id).await?;
let remove2 = alice.pop_sent_msg().await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&alice, alice_chat_id).await?.len(), 2);
// Bob receives the add and deletion messages out of order
let bob = TestContext::new_bob().await;
bob.recv_msg(&add1).await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
bob.recv_msg(&add3).await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
bob.recv_msg(&add2).await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
let bob_chat_id = bob.get_last_msg().await.chat_id;
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 4);
bob.recv_msg(&remove2).await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
bob.recv_msg(&remove1).await;
async_std::task::sleep(std::time::Duration::from_millis(1100)).await;
assert_eq!(get_chat_contacts(&bob, bob_chat_id).await?.len(), 2);
Ok(())
}
#[async_std::test]
async fn test_add_remove_contact_for_single() {
let ctx = TestContext::new_alice().await;

View File

@@ -64,6 +64,13 @@ pub enum Config {
#[strum(props(default = "1"))]
E2eeEnabled,
/// Ignore Autocrypt recommendation for message encryption if possible.
///
/// The only expection is when recommendation is "disable", i.e. encryption is not possible
/// because some recipient has no OpenPGP key.
#[strum(props(default = "0"))]
E2eeForce,
#[strum(props(default = "1"))]
MdnsEnabled,

View File

@@ -221,9 +221,7 @@ async fn configure(ctx: &Context, param: &mut LoginParam) -> Result<()> {
"checking internal provider-info for offline autoconfig"
);
if let Some(provider) =
provider::get_provider_info(ctx, &param_domain, socks5_enabled).await
{
if let Some(provider) = provider::get_provider_info(&param_domain, socks5_enabled).await {
param.provider = Some(provider);
match provider.status {
provider::Status::Ok | provider::Status::Preparation => {

View File

@@ -307,6 +307,7 @@ impl Context {
.await?
.unwrap_or_else(|| "unknown".to_string());
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).await?;
let e2ee_force = self.get_config_int(Config::E2eeForce).await?;
let mdns_enabled = self.get_config_int(Config::MdnsEnabled).await?;
let bcc_self = self.get_config_int(Config::BccSelf).await?;
let send_sync_msgs = self.get_config_int(Config::SendSyncMsgs).await?;
@@ -394,6 +395,7 @@ impl Context {
res.insert("configured_mvbox_folder", configured_mvbox_folder);
res.insert("mdns_enabled", mdns_enabled.to_string());
res.insert("e2ee_enabled", e2ee_enabled.to_string());
res.insert("e2ee_force", e2ee_force.to_string());
res.insert(
"key_gen_type",
self.get_config_int(Config::KeyGenType).await?.to_string(),

View File

@@ -825,18 +825,6 @@ async fn add_parts(
}
}
if let Some(chat_id) = chat_id {
apply_group_changes(
context,
mime_parser,
sent_timestamp,
chat_id,
from_id,
to_ids,
)
.await?;
}
if chat_id.is_none() && self_sent {
// from_id==to_id==DC_CONTACT_ID_SELF - this is a self-sent messages,
// maybe an Autocrypt Setup Message
@@ -1706,14 +1694,13 @@ async fn apply_group_changes(
send_event_chat_modified = true;
}
} else if let Some(contact_id) = removed_id {
// in contrast to `Chat-Group-Member-Added` we do not reconstruct the whole state from To: on removing -
// otherwise out-of-order removals won't work as members are re-added quickly.
//
// therefore, we need to ignore `MemberListTimestamp`
// and execute `Chat-Group-Member-Removed` statements even when they arrive out of order.
// we do not set `MemberListTimestamp` as we do not reconstuct the memberlist.
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
send_event_chat_modified = true;
if chat_id
.update_timestamp(context, Param::MemberListTimestamp, sent_timestamp)
.await?
{
chat::remove_from_chat_contacts_table(context, chat_id, contact_id).await?;
send_event_chat_modified = true;
}
}
if send_event_chat_modified {

View File

@@ -19,6 +19,7 @@ use crate::pgp;
#[derive(Debug)]
pub struct EncryptHelper {
pub prefer_encrypt: EncryptPreference,
force_preference: bool,
pub addr: String,
pub public_key: SignedPublicKey,
}
@@ -28,6 +29,7 @@ impl EncryptHelper {
let prefer_encrypt =
EncryptPreference::from_i32(context.get_config_int(Config::E2eeEnabled).await?)
.unwrap_or_default();
let force_preference = context.get_config_bool(Config::E2eeForce).await?;
let addr = match context.get_config(Config::ConfiguredAddr).await? {
None => {
bail!("addr not configured!");
@@ -39,6 +41,7 @@ impl EncryptHelper {
Ok(EncryptHelper {
prefer_encrypt,
force_preference,
addr,
public_key,
})
@@ -100,11 +103,17 @@ impl EncryptHelper {
}
}
// Count number of recipients, including self.
// This does not depend on whether we send a copy to self or not.
let recipients_count = peerstates.len() + 1;
let want_encrypt = if self.force_preference {
// Ignore preferences of others.
self.prefer_encrypt == EncryptPreference::Mutual
} else {
// Count number of recipients, including self.
// This does not depend on whether we send a copy to self or not.
let recipients_count = peerstates.len() + 1;
2 * prefer_encrypt_count > recipients_count
};
Ok(e2ee_guaranteed || 2 * prefer_encrypt_count > recipients_count)
Ok(e2ee_guaranteed || want_encrypt)
}
/// Tries to encrypt the passed in `mail`.
@@ -381,6 +390,7 @@ mod tests {
use crate::chat;
use crate::constants::Viewtype;
use crate::dc_receive_imf::dc_receive_imf;
use crate::message::Message;
use crate::param::Param;
use crate::peerstate::ToSave;
@@ -602,4 +612,68 @@ Sent with my Delta Chat Messenger: https://delta.chat";
Ok(())
}
#[async_std::test]
async fn test_e2ee_force() -> Result<()> {
let alice = TestContext::new_alice().await;
let bob = TestContext::new_bob().await;
let alice_chat = alice.create_chat(&bob).await;
let bob_chat = bob.create_chat(&alice).await;
alice.set_config(Config::ShowEmails, Some("2")).await?;
bob.set_config(Config::ShowEmails, Some("2")).await?;
// Alice does not prefer encryption.
alice.set_config(Config::E2eeEnabled, Some("0")).await?;
bob.set_config(Config::E2eeEnabled, Some("1")).await?;
// Alice sends her key to Bob.
let sent_msg = alice.send_text(alice_chat.id, "Hi Bob").await;
bob.recv_msg(&sent_msg).await;
let received_msg = bob.get_last_msg().await;
assert!(!received_msg.get_showpadlock());
// Bob should not encrypt, because Alice does not prefer encryption.
let sent_msg = bob
.send_text(bob_chat.id, "This should not be encrypted")
.await;
alice.recv_msg(&sent_msg).await;
let received_msg = alice.get_last_msg().await;
assert!(!received_msg.get_showpadlock());
// Bob ignores Alice's preference for no encryption.
bob.set_config(Config::E2eeForce, Some("1")).await?;
let sent_msg = bob.send_text(bob_chat.id, "This should be encrypted").await;
alice.recv_msg(&sent_msg).await;
let received_msg = alice.get_last_msg().await;
assert!(received_msg.get_showpadlock());
// Alice switches to MUA without Autocrypt support.
dc_receive_imf(
&bob,
br#"Subject: Hello from MUA
Message-ID: foobar@example.com
To: Bob <bob@example.net>
From: Alice <alice@example.com>
Content-Type: text/plain; charset=utf-8
Date: Sun, 14 Mar 2500 00:00:00 +0000
Hello from MUA."#,
"INBOX",
100,
false,
)
.await?;
// Bob can't encrypt now because Alice has no key.
let sent_msg = bob
.send_text(bob_chat.id, "This should not be encrypted again")
.await;
alice.recv_msg(&sent_msg).await;
let received_msg = alice.get_last_msg().await;
assert!(!received_msg.get_showpadlock());
Ok(())
}
}

View File

@@ -58,7 +58,7 @@ pub async fn dc_get_oauth2_url(
redirect_uri: &str,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
context
.sql
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
@@ -79,7 +79,7 @@ pub async fn dc_get_oauth2_access_token(
regenerate: bool,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
if let Some(oauth2) = Oauth2::from_address(context, addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(addr, socks5_enabled).await {
let lock = context.oauth2_mutex.lock().await;
// read generated token
@@ -225,7 +225,7 @@ pub async fn dc_get_oauth2_addr(
code: &str,
) -> Result<Option<String>> {
let socks5_enabled = context.get_config_bool(Config::Socks5Enabled).await?;
let oauth2 = match Oauth2::from_address(context, addr, socks5_enabled).await {
let oauth2 = match Oauth2::from_address(addr, socks5_enabled).await {
Some(o) => o,
None => return Ok(None),
};
@@ -253,13 +253,13 @@ pub async fn dc_get_oauth2_addr(
}
impl Oauth2 {
async fn from_address(context: &Context, addr: &str, skip_mx: bool) -> Option<Self> {
async fn from_address(addr: &str, skip_mx: bool) -> Option<Self> {
let addr_normalized = normalize_addr(addr);
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)
if let Some(oauth2_authorizer) = provider::get_provider_info(domain, skip_mx)
.await
.and_then(|provider| provider.oauth2_authorizer.as_ref())
{
@@ -356,39 +356,32 @@ mod tests {
#[async_std::test]
async fn test_oauth_from_address() {
let t = TestContext::new().await;
assert_eq!(
Oauth2::from_address(&t, "hello@gmail.com", false).await,
Oauth2::from_address("hello@gmail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address(&t, "hello@googlemail.com", false).await,
Oauth2::from_address("hello@googlemail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address(&t, "hello@yandex.com", false).await,
Oauth2::from_address("hello@yandex.com", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(
Oauth2::from_address(&t, "hello@yandex.ru", false).await,
Oauth2::from_address("hello@yandex.ru", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(Oauth2::from_address(&t, "hello@web.de", false).await, None);
assert_eq!(Oauth2::from_address("hello@web.de", false).await, None);
}
#[async_std::test]
async fn test_oauth_from_mx() {
// youtube staff seems to use "google workspace with oauth2", figures this out by MX lookup
let t = TestContext::new().await;
assert_eq!(
Oauth2::from_address(&t, "hello@youtube.com", false).await,
Oauth2::from_address("hello@google.com", false).await,
Some(OAUTH2_GMAIL)
);
// without MX lookup, we would not know as youtube.com is not in our provider-db
assert_eq!(
Oauth2::from_address(&t, "hello@youtube.com", true).await,
None
);
}
#[async_std::test]

View File

@@ -191,11 +191,6 @@ impl fmt::Display for Params {
impl str::FromStr for Params {
type Err = Error;
/// Parse a raw string to Param.
///
/// Silently ignore unknown keys:
/// they may come from a downgrade (when a shortly new version adds a key)
/// or from an upgrade (when a key is dropped but was used in the past)
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let mut inner = BTreeMap::new();
let mut lines = s.lines().peekable();
@@ -215,6 +210,8 @@ impl str::FromStr for Params {
if let Some(key) = key.as_bytes().first().and_then(|key| Param::from_u8(*key)) {
inner.insert(key, value);
} else {
bail!("Unknown key: {}", key);
}
} else {
bail!("Not a key-value pair: {:?}", line);
@@ -417,12 +414,10 @@ impl<'a> ParamsFile<'a> {
mod tests {
use super::*;
use anyhow::Result;
use async_std::fs;
use async_std::path::Path;
use crate::test_utils::TestContext;
use std::str::FromStr;
#[test]
fn test_dc_param() {
@@ -525,14 +520,4 @@ mod tests {
assert!(p.get_path(Param::File, &t).unwrap().is_none());
assert!(p.get_blob(Param::File, &t, false).await.unwrap().is_none());
}
#[async_std::test]
async fn test_params_unknown_key() -> Result<()> {
// 'Z' is used as a key that is known to be unused; these keys should be ignored silently by definition.
let p = Params::from_str("w=12\nZ=13\nh=14")?;
assert_eq!(p.len(), 2);
assert_eq!(p.get(Param::Width), Some("12"));
assert_eq!(p.get(Param::Height), Some("14"));
Ok(())
}
}

View File

@@ -3,10 +3,8 @@
mod data;
use crate::config::Config;
use crate::context::Context;
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS, PROVIDER_UPDATED};
use anyhow::Result;
use async_std_resolver::{config, resolver, resolver_from_system_conf, AsyncStdResolver};
use async_std_resolver::resolver_from_system_conf;
use chrono::{NaiveDateTime, NaiveTime};
#[derive(Debug, Display, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
@@ -83,23 +81,6 @@ pub struct Provider {
pub oauth2_authorizer: Option<Oauth2Authorizer>,
}
/// Get resolver to query MX records.
///
/// We first try resolver_from_system_conf() which reads the system's resolver from `/etc/resolv.conf`.
/// This does not work at least on some Androids, therefore we use use ResolverConfig::default()
/// which default eg. to google's 8.8.8.8 or 8.8.4.4 as a fallback.
async fn get_resolver() -> Result<AsyncStdResolver> {
if let Ok(resolver) = resolver_from_system_conf().await {
return Ok(resolver);
}
let resolver = resolver(
config::ResolverConfig::default(),
config::ResolverOpts::default(),
)
.await?;
Ok(resolver)
}
/// Returns provider for the given domain.
///
/// This function looks up domain in offline database first. If not
@@ -108,11 +89,7 @@ async fn get_resolver() -> Result<AsyncStdResolver> {
///
/// For compatibility, email address can be passed to this function
/// instead of the domain.
pub async fn get_provider_info(
context: &Context,
domain: &str,
skip_mx: bool,
) -> Option<&'static Provider> {
pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> {
let domain = domain.rsplitn(2, '@').next()?;
if let Some(provider) = get_provider_by_domain(domain) {
@@ -120,7 +97,7 @@ pub async fn get_provider_info(
}
if !skip_mx {
if let Some(provider) = get_provider_by_mx(context, domain).await {
if let Some(provider) = get_provider_by_mx(domain).await {
return Some(provider);
}
}
@@ -140,8 +117,8 @@ pub fn get_provider_by_domain(domain: &str) -> Option<&'static Provider> {
/// 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> {
if let Ok(resolver) = get_resolver().await {
pub async fn get_provider_by_mx(domain: &str) -> Option<&'static Provider> {
if let Ok(resolver) = resolver_from_system_conf().await {
let mut fqdn: String = domain.to_string();
if !fqdn.ends_with('.') {
fqdn.push('.');
@@ -166,8 +143,6 @@ pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'sta
}
}
}
} else {
warn!(context, "cannot get a resolver to check MX records.");
}
None
@@ -194,7 +169,6 @@ mod tests {
use super::*;
use crate::dc_tools::time;
use crate::test_utils::TestContext;
use chrono::NaiveDate;
#[test]
@@ -244,13 +218,12 @@ mod tests {
#[async_std::test]
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("", false).await.is_none());
assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail");
// get_provider_info() accepts email addresses for backwards compatibility
assert!(
get_provider_info(&t, "example@google.com", false)
get_provider_info("example@google.com", false)
.await
.unwrap()
.id
@@ -269,10 +242,4 @@ mod tests {
assert!(get_provider_update_timestamp() <= time());
assert!(get_provider_update_timestamp() > timestamp_past);
}
#[async_std::test]
async fn test_get_resolver() -> Result<()> {
assert!(get_resolver().await.is_ok());
Ok(())
}
}