Compare commits

...

5 Commits

Author SHA1 Message Date
bjoern
5ce2581b4c prepare 1.69 (#2855)
* update changelog for 1.69.0

* bump version to 1.69.0
2021-11-30 14:23:27 +01:00
bjoern
c0d6c6b882 use anyhow-result for get_resolver (#2853)
* use anyhow-result for get_resolver

* prefer '?' over map_err()
2021-11-30 13:57:04 +01:00
bjoern
2bc8c967b1 really test MX lookup in test_oauth_from_mx() (#2854) 2021-11-30 13:55:07 +01:00
bjoern
fee08f0eeb fix missing MX resolver eg. on android (#2852)
* fix missing MX resolver eg. on android

switching completely to /etc/resolv.conf (see #2780)
does not work at least on some Androids
(see https://github.com/deltachat/deltachat-android/issues/2151 )

therefore, we use the old approach as a fallback.

* log a warning, when we again have problems with figuring out MX resolvers
2021-11-30 11:23:49 +01:00
bjoern
078c3d05d7 fix group changes in multi device setup (#2848)
* test adding members in a multi-device setup

* fix system messages for multi-device-setup

* enhance test to check multi-device messages for removing-members and group-renames
2021-11-30 00:31:36 +01:00
11 changed files with 171 additions and 27 deletions

View File

@@ -1,5 +1,12 @@
# Changelog
## 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.68.0"
version = "1.69.0"
dependencies = [
"ansi_term",
"anyhow",
@@ -1136,7 +1136,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.68.0"
version = "1.69.0"
dependencies = [
"anyhow",
"async-std",

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.68.0"
version = "1.69.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.68.0"
version = "1.69.0"
description = "Deltachat FFI"
authors = ["Delta Chat Developers (ML) <delta@codespeak.net>"]
edition = "2018"

View File

@@ -3753,7 +3753,11 @@ pub unsafe extern "C" fn dc_provider_new_from_email(
match socks5_enabled {
Ok(socks5_enabled) => {
match block_on(provider::get_provider_info(addr.as_str(), socks5_enabled)) {
match block_on(provider::get_provider_info(
ctx,
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(arg1, socks5_enabled).await {
match provider::get_provider_info(&context, arg1, socks5_enabled).await {
Some(info) => {
println!("Information for provider belonging to {}:", arg1);
println!("status: {}", info.status as u32);

View File

@@ -3225,6 +3225,85 @@ 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_add_remove_contact_for_single() {
let ctx = TestContext::new_alice().await;

View File

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

View File

@@ -825,6 +825,18 @@ 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

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(addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(context, 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(addr, socks5_enabled).await {
if let Some(oauth2) = Oauth2::from_address(context, 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(addr, socks5_enabled).await {
let oauth2 = match Oauth2::from_address(context, 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(addr: &str, skip_mx: bool) -> Option<Self> {
async fn from_address(context: &Context, 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(domain, skip_mx)
if let Some(oauth2_authorizer) = provider::get_provider_info(context, domain, skip_mx)
.await
.and_then(|provider| provider.oauth2_authorizer.as_ref())
{
@@ -356,32 +356,39 @@ mod tests {
#[async_std::test]
async fn test_oauth_from_address() {
let t = TestContext::new().await;
assert_eq!(
Oauth2::from_address("hello@gmail.com", false).await,
Oauth2::from_address(&t, "hello@gmail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address("hello@googlemail.com", false).await,
Oauth2::from_address(&t, "hello@googlemail.com", false).await,
Some(OAUTH2_GMAIL)
);
assert_eq!(
Oauth2::from_address("hello@yandex.com", false).await,
Oauth2::from_address(&t, "hello@yandex.com", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(
Oauth2::from_address("hello@yandex.ru", false).await,
Oauth2::from_address(&t, "hello@yandex.ru", false).await,
Some(OAUTH2_YANDEX)
);
assert_eq!(Oauth2::from_address("hello@web.de", false).await, None);
assert_eq!(Oauth2::from_address(&t, "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("hello@google.com", false).await,
Oauth2::from_address(&t, "hello@youtube.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

@@ -3,8 +3,10 @@
mod data;
use crate::config::Config;
use crate::context::Context;
use crate::provider::data::{PROVIDER_DATA, PROVIDER_IDS, PROVIDER_UPDATED};
use async_std_resolver::resolver_from_system_conf;
use anyhow::Result;
use async_std_resolver::{config, resolver, resolver_from_system_conf, AsyncStdResolver};
use chrono::{NaiveDateTime, NaiveTime};
#[derive(Debug, Display, Copy, Clone, PartialEq, FromPrimitive, ToPrimitive)]
@@ -81,6 +83,23 @@ 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
@@ -89,7 +108,11 @@ pub struct Provider {
///
/// For compatibility, email address can be passed to this function
/// instead of the domain.
pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static Provider> {
pub async fn get_provider_info(
context: &Context,
domain: &str,
skip_mx: bool,
) -> Option<&'static Provider> {
let domain = domain.rsplitn(2, '@').next()?;
if let Some(provider) = get_provider_by_domain(domain) {
@@ -97,7 +120,7 @@ pub async fn get_provider_info(domain: &str, skip_mx: bool) -> Option<&'static P
}
if !skip_mx {
if let Some(provider) = get_provider_by_mx(domain).await {
if let Some(provider) = get_provider_by_mx(context, domain).await {
return Some(provider);
}
}
@@ -117,8 +140,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(domain: &str) -> Option<&'static Provider> {
if let Ok(resolver) = resolver_from_system_conf().await {
pub async fn get_provider_by_mx(context: &Context, domain: &str) -> Option<&'static Provider> {
if let Ok(resolver) = get_resolver().await {
let mut fqdn: String = domain.to_string();
if !fqdn.ends_with('.') {
fqdn.push('.');
@@ -143,6 +166,8 @@ pub async fn get_provider_by_mx(domain: &str) -> Option<&'static Provider> {
}
}
}
} else {
warn!(context, "cannot get a resolver to check MX records.");
}
None
@@ -169,6 +194,7 @@ mod tests {
use super::*;
use crate::dc_tools::time;
use crate::test_utils::TestContext;
use chrono::NaiveDate;
#[test]
@@ -218,12 +244,13 @@ mod tests {
#[async_std::test]
async fn test_get_provider_info() {
assert!(get_provider_info("", false).await.is_none());
assert!(get_provider_info("google.com", false).await.unwrap().id == "gmail");
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");
// get_provider_info() accepts email addresses for backwards compatibility
assert!(
get_provider_info("example@google.com", false)
get_provider_info(&t, "example@google.com", false)
.await
.unwrap()
.id
@@ -242,4 +269,10 @@ 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(())
}
}