Compare commits

..

22 Commits

Author SHA1 Message Date
iequidoo
67771735a9 feat: Move unverified messages which don't introduce a new Autocrypt key from protected 1:1 chats to ad-hoc groups
This prevents protected 1:1 chats from breaking by messages sent by classical MUAs.

TODO: Some `tests::verified_chats` tests are failing, but as far as i see from the log, the tests
must be fixed, the change itself looks ok. Not fixing the tests now, not sure we will merge this as
it's just POC.
2023-10-26 00:41:59 -03:00
iequidoo
0ea1e93567 feat: Move unverified messages carrying current Autocrypt key from protected 1:1 chats to ad-hoc groups
TODO: This should be squashed with the previous commit if we decide to go this way.
2023-10-26 00:41:59 -03:00
iequidoo
83fa355291 fix: receive_imf: Don't break 1:1 chat protection if received the verified Autocrypt key (#4597)
For example, broadcast list messages are sent unencrypted, but their Autocrypt header still contains
the verified key. Thus we're sure that we still can encrypt to the peer and there's no need to break
the existing protection.
2023-10-26 00:41:59 -03:00
link2xt
25a78aceb9 ci: increase MSRV to 1.70.0
This is required by `anstyle v1.0.4` dependency.
2023-10-26 02:16:10 +00:00
link2xt
66708454dd chore: update dependencies 2023-10-26 02:13:15 +00:00
link2xt
bb5e3d11d8 chore: update futures-lite dependency 2023-10-26 01:11:11 +00:00
link2xt
ff54db2e5f test: adapt the test for updated chrono 2023-10-26 00:47:32 +00:00
link2xt
434d8fc35f chore: update quick-xml 2023-10-25 22:42:14 +00:00
link2xt
12eb813bc3 ci(concourse): replace master branch with main 2023-10-25 22:02:35 +00:00
link2xt
88d5576150 ci(github): replace references to master branch with main 2023-10-25 22:02:32 +00:00
link2xt
af35e4adeb chore: update dependencies 2023-10-25 21:58:13 +00:00
link2xt
eaeacb8848 ci: run only on main branch pushes 2023-10-25 21:46:09 +00:00
link2xt
f00e68e142 chore: update some dependencies 2023-10-25 21:45:22 +00:00
link2xt
113356a24e docs: fix CI badge URL in the readme 2023-10-25 19:31:22 +00:00
link2xt
b89c134e7f Merge branch 'master' into stable 2023-10-25 16:50:18 +00:00
iequidoo
ccca12176e feat: Replace Config::SendSyncMsgs with SyncMsgs (#4817)
And execute sync messages only if `Config::SyncMsgs` is enabled. Earlier executing was always
enabled, the messages are force-encrypted anyway. But for users it's probably more clear whether a
device is synchronised or not.
2023-10-25 04:47:37 -03:00
dependabot[bot]
c89dd331f7 chore(cargo): bump libc from 0.2.147 to 0.2.149
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.147 to 0.2.149.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.147...0.2.149)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-25 03:10:25 +00:00
link2xt
fa81ed5f39 ci: rename async tests into JSON-RPC tests 2023-10-25 01:33:56 +00:00
link2xt
6c34f6b8d9 ci: remove misplaced comment 2023-10-25 01:33:56 +00:00
dependabot[bot]
4f21a5691d chore(cargo): bump tokio from 1.29.1 to 1.33.0
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.29.1 to 1.33.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.29.1...tokio-1.33.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-25 00:56:33 +00:00
dependabot[bot]
b2a839971b chore(cargo): bump strum_macros from 0.25.1 to 0.25.3
Bumps [strum_macros](https://github.com/Peternator7/strum) from 0.25.1 to 0.25.3.
- [Changelog](https://github.com/Peternator7/strum/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Peternator7/strum/commits)

---
updated-dependencies:
- dependency-name: strum_macros
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-24 23:01:25 +00:00
link2xt
8ad99be322 ci: increase MSRV to 1.67.0 2023-10-24 21:18:15 +00:00
25 changed files with 545 additions and 2681 deletions

View File

@@ -14,8 +14,7 @@ on:
pull_request:
push:
branches:
- master
- stable
- main
env:
RUSTFLAGS: -Dwarnings
@@ -83,9 +82,9 @@ jobs:
- os: macos-latest
rust: 1.73.0
# Minimum Supported Rust Version = 1.67.0
# Minimum Supported Rust Version = 1.70.0
- os: ubuntu-latest
rust: 1.67.0
rust: 1.70.0
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
@@ -167,8 +166,8 @@ jobs:
working-directory: deltachat-rpc-client
run: tox -e lint
python_tests:
name: Python tests
cffi_python_tests:
name: CFFI Python tests
needs: ["c_library", "python_lint"]
strategy:
fail-fast: false
@@ -218,8 +217,8 @@ jobs:
working-directory: python
run: tox -e mypy,doc,py
aysnc_python_tests:
name: Async Python tests
rpc_python_tests:
name: JSON-RPC Python tests
needs: ["python_lint", "rpc_server"]
strategy:
fail-fast: false

View File

@@ -2,9 +2,9 @@ name: JSON-RPC API Test
on:
push:
branches: [master]
branches: [main]
pull_request:
branches: [master]
branches: [main]
env:
CARGO_TERM_COLOR: always

View File

@@ -8,7 +8,7 @@ name: Generate & upload node.js documentation
on:
push:
branches:
- master
- main
jobs:
generate:

View File

@@ -13,7 +13,7 @@ on:
pull_request:
push:
branches:
- master
- main
jobs:
tests:

View File

@@ -3,8 +3,7 @@ name: Build & Deploy Documentation on rs.delta.chat
on:
push:
branches:
- master
- docs-gh-action
- main
jobs:
build:

View File

@@ -7,8 +7,7 @@ name: Build & Deploy Documentation on cffi.delta.chat
on:
push:
branches:
- master
- docs-gh-action
- main
jobs:
build:

2775
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ name = "deltachat"
version = "1.126.1"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.67"
rust-version = "1.70"
[profile.dev]
debug = 0
@@ -51,13 +51,12 @@ escaper = "0.1"
fast-socks5 = "0.8"
fd-lock = "3.0.11"
futures = "0.3"
futures-lite = "1.13.0"
futures-lite = "2.0.0"
hex = "0.4.0"
hickory-resolver = "0.24"
humansize = "2"
image = { version = "0.24.7", default-features=false, features = ["gif", "jpeg", "ico", "png", "pnm", "webp", "bmp"] }
iroh04 = { package = "iroh", version = "0.4.1", default-features = false }
iroh = "0.11"
iroh = { version = "0.4.1", default-features = false }
kamadak-exif = "0.5"
lettre_email = { git = "https://github.com/deltachat/lettre", branch = "master" }
libc = "0.2"
@@ -72,7 +71,7 @@ parking_lot = "0.12"
pgp = { version = "0.10", default-features = false }
pretty_env_logger = { version = "0.5", optional = true }
qrcodegen = "1.7.0"
quick-xml = "0.30"
quick-xml = "0.31"
rand = "0.8"
regex = "1.9"
reqwest = { version = "0.11.20", features = ["json"] }
@@ -97,12 +96,11 @@ tokio-util = "0.7.9"
toml = "0.7"
url = "2"
uuid = { version = "1", features = ["serde", "v4"] }
quic-rpc = "0.6.1"
[dev-dependencies]
ansi_term = "0.12.0"
criterion = { version = "0.5.1", features = ["async_tokio"] }
futures-lite = "1.13"
futures-lite = "2.0.0"
log = "0.4"
pretty_env_logger = "0.5"
proptest = { version = "1", default-features = false, features = ["std"] }

View File

@@ -3,8 +3,8 @@
</p>
<p align="center">
<a href="https://github.com/yoav-lavi/melody/actions/workflows/rust.yml">
<img alt="Rust CI" src="https://github.com/yoav-lavi/melody/actions/workflows/rust.yml/badge.svg">
<a href="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml">
<img alt="Rust CI" src="https://github.com/deltachat/deltachat-core-rust/actions/workflows/ci.yml/badge.svg">
</a>
</p>

View File

@@ -24,7 +24,7 @@ futures = { version = "0.3.28" }
serde_json = "1.0.105"
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }
typescript-type-def = { version = "0.5.8", features = ["json_value"] }
tokio = { version = "1.32.0" }
tokio = { version = "1.33.0" }
sanitize-filename = "0.5"
walkdir = "2.3.3"
base64 = "0.21"
@@ -34,7 +34,7 @@ axum = { version = "0.6.20", optional = true, features = ["ws"] }
env_logger = { version = "0.10.0", optional = true }
[dev-dependencies]
tokio = { version = "1.32.0", features = ["full", "rt-multi-thread"] }
tokio = { version = "1.33.0", features = ["full", "rt-multi-thread"] }
[features]

View File

@@ -15,11 +15,11 @@ deltachat = { path = "..", default-features = false }
anyhow = "1"
env_logger = { version = "0.10.0" }
futures-lite = "1.13.0"
futures-lite = "2.0.0"
log = "0.4"
serde_json = "1.0.105"
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.32.0", features = ["io-std"] }
tokio = { version = "1.33.0", features = ["io-std"] }
tokio-util = "0.7.9"
yerpc = { version = "0.5.2", features = ["anyhow_expose", "openrpc"] }

View File

@@ -24,7 +24,6 @@ skip = [
{ name = "digest", version = "<0.10" },
{ name = "ed25519-dalek", version = "1.0.1" },
{ name = "ed25519", version = "1.5.3" },
{ name = "fastrand", version = "1.9.0" },
{ name = "getrandom", version = "<0.2" },
{ name = "hashbrown", version = "<0.14.0" },
{ name = "indexmap", version = "<2.0.0" },

View File

@@ -270,7 +270,7 @@ describe('Basic offline Tests', function () {
'quota_exceeding',
'scan_all_folders_debounce_secs',
'selfavatar',
'send_sync_msgs',
'sync_msgs',
'sentbox_watch',
'show_emails',
'socks5_enabled',

View File

@@ -3,14 +3,14 @@ resources:
type: git
icon: github
source:
branch: master
branch: main
uri: https://github.com/deltachat/deltachat-core-rust.git
- name: deltachat-core-rust-release
type: git
icon: github
source:
branch: master
branch: main
uri: https://github.com/deltachat/deltachat-core-rust.git
tag_filter: "v*"

View File

@@ -6,7 +6,7 @@ use std::str::FromStr;
use anyhow::{ensure, Context as _, Result};
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumString};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty, EnumString};
use crate::blob::BlobObject;
use crate::constants::DC_VERSION_STR;
@@ -297,10 +297,9 @@ pub enum Config {
#[strum(props(default = "0"))]
DownloadLimit,
/// Send sync messages, requires `BccSelf` to be set as well.
/// In a future versions, this switch may be removed.
/// Enable sending and executing (applying) sync messages. Sending requires `BccSelf` to be set.
#[strum(props(default = "0"))]
SendSyncMsgs,
SyncMsgs,
/// Space-separated list of all the authserv-ids which we believe
/// may be the one of our email server.
@@ -335,9 +334,6 @@ pub enum Config {
/// until `chat_id.accept()` is called.
#[strum(props(default = "0"))]
VerifiedOneOnOneChats,
/// The iroh document ticket.
DocTicket,
}
impl Context {
@@ -504,7 +500,7 @@ impl Context {
| Config::Configured
| Config::Bot
| Config::NotifyAboutWrongPw
| Config::SendSyncMsgs
| Config::SyncMsgs
| Config::SignUnencrypted
| Config::DisableIdle => {
ensure!(

View File

@@ -4,7 +4,6 @@ use std::collections::{BTreeMap, HashMap};
use std::ffi::OsString;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant, SystemTime};
@@ -245,8 +244,6 @@ pub struct InnerContext {
/// Standard RwLock instead of [`tokio::sync::RwLock`] is used
/// because the lock is used from synchronous [`Context::emit_event`].
pub(crate) debug_logging: std::sync::RwLock<Option<DebugLogging>>,
pub(crate) iroh_node: iroh::node::Node<iroh::bytes::store::flat::Store>,
}
/// The state of ongoing process.
@@ -315,10 +312,7 @@ impl Context {
if !blobdir.exists() {
tokio::fs::create_dir_all(&blobdir).await?;
}
let irohdir = dbfile.with_file_name("iroh");
let context =
Context::with_blobdir(dbfile.into(), blobdir, irohdir, id, events, stockstrings)
.await?;
let context = Context::with_blobdir(dbfile.into(), blobdir, id, events, stockstrings)?;
Ok(context)
}
@@ -355,10 +349,9 @@ impl Context {
self.sql.check_passphrase(passphrase).await
}
pub(crate) async fn with_blobdir(
pub(crate) fn with_blobdir(
dbfile: PathBuf,
blobdir: PathBuf,
irohdir: PathBuf,
id: u32,
events: Events,
stockstrings: StockStrings,
@@ -369,31 +362,6 @@ impl Context {
blobdir.display()
);
tokio::fs::create_dir_all(&irohdir).await?;
let keyfile = irohdir.join("secret-key");
let key = if tokio::fs::try_exists(&keyfile).await? {
let s = tokio::fs::read_to_string(&keyfile).await?;
iroh::net::key::SecretKey::from_str(&s)?
} else {
let key = iroh::net::key::SecretKey::generate();
tokio::fs::write(keyfile, key.to_string()).await?;
key
};
let rt = iroh::bytes::util::runtime::Handle::from_current(1)?;
let baostore = iroh::bytes::store::flat::Store::load(
irohdir.join("complete"),
irohdir.join("partial"),
irohdir.join("meta"),
&rt,
)
.await?;
let docstore = iroh::sync::store::fs::Store::new(irohdir.join("docs"))?;
let iroh_node = iroh::node::Node::builder(baostore, docstore)
.secret_key(key)
.runtime(&rt)
.spawn()
.await?;
let new_msgs_notify = Notify::new();
// Notify once immediately to allow processing old messages
// without starting I/O.
@@ -420,7 +388,6 @@ impl Context {
last_full_folder_scan: Mutex::new(None),
last_error: std::sync::RwLock::new("".to_string()),
debug_logging: std::sync::RwLock::new(None),
iroh_node,
};
let ctx = Context {
@@ -437,19 +404,6 @@ impl Context {
return;
}
self.scheduler.start(self.clone()).await;
if let Ok(Some(ticket)) = self.get_config(Config::DocTicket).await {
if let Err(err) = self.start_iroh(ticket).await {
error!(self, "failed to join iroh doc, {err:#}");
}
}
}
async fn start_iroh(&self, ticket: String) -> Result<()> {
let client = self.iroh_node.client();
let _doc = client.docs.import(ticket.parse()?).await?;
Ok(())
}
/// Stops the IO scheduler.
@@ -630,7 +584,7 @@ impl Context {
let e2ee_enabled = self.get_config_int(Config::E2eeEnabled).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?;
let sync_msgs = self.get_config_int(Config::SyncMsgs).await?;
let disable_idle = self.get_config_bool(Config::DisableIdle).await?;
let prv_key_cnt = self.sql.count("SELECT COUNT(*) FROM keypairs;", ()).await?;
@@ -743,7 +697,7 @@ impl Context {
self.get_config_int(Config::KeyGenType).await?.to_string(),
);
res.insert("bcc_self", bcc_self.to_string());
res.insert("send_sync_msgs", send_sync_msgs.to_string());
res.insert("sync_msgs", sync_msgs.to_string());
res.insert("disable_idle", disable_idle.to_string());
res.insert("private_key_count", prv_key_cnt.to_string());
res.insert("public_key_count", pub_key_cnt.to_string());
@@ -1288,16 +1242,7 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = PathBuf::new();
let irohdir = tmp.path().join("iroh");
let res = Context::with_blobdir(
dbfile,
blobdir,
irohdir,
1,
Events::new(),
StockStrings::new(),
)
.await;
let res = Context::with_blobdir(dbfile, blobdir, 1, Events::new(), StockStrings::new());
assert!(res.is_err());
}
@@ -1306,16 +1251,7 @@ mod tests {
let tmp = tempfile::tempdir().unwrap();
let dbfile = tmp.path().join("db.sqlite");
let blobdir = tmp.path().join("blobs");
let irohdir = tmp.path().join("iroh");
let res = Context::with_blobdir(
dbfile,
blobdir,
irohdir,
1,
Events::new(),
StockStrings::new(),
)
.await;
let res = Context::with_blobdir(dbfile, blobdir, 1, Events::new(), StockStrings::new());
assert!(res.is_err());
}

View File

@@ -1379,100 +1379,12 @@ impl Imap {
Ok(msgs.into_iter().map(|((_, uid), msg)| (uid, msg)).collect())
}
pub(crate) async fn fetch_many_msgs(
&mut self,
context: &Context,
folder: &str,
request_uids: Vec<u32>,
uid_message_ids: &BTreeMap<u32, String>,
fetch_partially: bool,
fetching_existing_messages: bool,
) -> Result<(Option<u32>, Vec<ReceivedMsg>)> {
let client = context.iroh_node.client();
let n_docs = client.docs.list().await?.count().await;
if n_docs == 1 {
self.fetch_many_msgs_iroh(
context,
folder,
request_uids,
uid_message_ids,
fetch_partially,
fetching_existing_messages,
client,
)
.await
} else {
self.fetch_many_msgs_imap(
context,
folder,
request_uids,
uid_message_ids,
fetch_partially,
fetching_existing_messages,
)
.await
}
}
pub(crate) async fn fetch_many_msgs_iroh(
&mut self,
context: &Context,
_folder: &str,
request_uids: Vec<u32>,
uid_message_ids: &BTreeMap<u32, String>,
_fetch_partially: bool,
_fetching_existing_messages: bool,
// client: iroh::client::quic::Iroh,
client: iroh::client::Iroh<
quic_rpc::transport::flume::FlumeConnection<
iroh::rpc_protocol::ProviderResponse,
iroh::rpc_protocol::ProviderRequest,
>,
>,
) -> Result<(Option<u32>, Vec<ReceivedMsg>)> {
let mut received_msgs = Vec::new();
let mut last_uid = None;
let (id, _caps) = client.docs.list().await?.next().await.context("no doc")??;
let doc = client.docs.open(id).await?.context("where is my doc?")?;
for request_uid in request_uids {
let rfc724_mid = if let Some(rfc724_mid) = uid_message_ids.get(&request_uid) {
rfc724_mid
} else {
error!(
context,
"No Message-ID corresponding to UID {} passed in uid_messsage_ids.",
request_uid
);
continue;
};
let key = format!("/inbox/{rfc724_mid}");
warn!(context, "Reading iroh key {key}");
let query = iroh::sync::store::Query::key_exact(&key).build();
let entry = doc.get_one(query).await?.context("entry not found")?;
let imf_raw = doc.read_to_bytes(&entry).await?;
info!(
context,
"Passing message UID {} to receive_imf().", request_uid
);
match receive_imf_inner(context, rfc724_mid, &imf_raw, false, None, false).await {
Ok(Some(msg)) => received_msgs.push(msg),
Ok(None) => (),
Err(err) => warn!(context, "receive_imf error: {err:#}"),
}
last_uid = Some(request_uid);
}
Ok((last_uid, received_msgs))
}
/// Fetches a list of messages by server UID.
///
/// Returns the last UID fetched successfully and the info about each downloaded message.
/// If the message is incorrect or there is a failure to write a message to the database,
/// it is skipped and the error is logged.
pub(crate) async fn fetch_many_msgs_imap(
pub(crate) async fn fetch_many_msgs(
&mut self,
context: &Context,
folder: &str,

View File

@@ -32,12 +32,12 @@ use std::task::Poll;
use anyhow::{anyhow, bail, ensure, format_err, Context as _, Result};
use async_channel::Receiver;
use futures_lite::StreamExt;
use iroh04::blobs::Collection;
use iroh04::get::DataStream;
use iroh04::progress::ProgressEmitter;
use iroh04::protocol::AuthToken;
use iroh04::provider::{DataSource, Event, Provider, Ticket};
use iroh04::Hash;
use iroh::blobs::Collection;
use iroh::get::DataStream;
use iroh::progress::ProgressEmitter;
use iroh::protocol::AuthToken;
use iroh::provider::{DataSource, Event, Provider, Ticket};
use iroh::Hash;
use tokio::fs::{self, File};
use tokio::io::{self, AsyncWriteExt, BufWriter};
use tokio::sync::broadcast::error::RecvError;
@@ -176,7 +176,7 @@ impl BackupProvider {
}
// Start listening.
let (db, hash) = iroh04::provider::create_collection(files).await?;
let (db, hash) = iroh::provider::create_collection(files).await?;
context.emit_event(SendProgress::CollectionCreated.into());
let provider = Provider::builder(db)
.bind_addr((Ipv4Addr::UNSPECIFIED, 0).into())
@@ -382,7 +382,7 @@ impl From<SendProgress> for EventType {
/// This is a long running operation which will only when completed.
///
/// Using [`Qr`] as argument is a bit odd as it only accepts one specific variant of it. It
/// does avoid having [`iroh04::provider::Ticket`] in the primary API however, without
/// does avoid having [`iroh::provider::Ticket`] in the primary API however, without
/// having to revert to untyped bytes.
pub async fn get_backup(context: &Context, qr: Qr) -> Result<()> {
ensure!(
@@ -456,7 +456,7 @@ async fn transfer_from_provider(context: &Context, ticket: &Ticket) -> Result<()
// Perform the transfer.
let keylog = false; // Do not enable rustls SSLKEYLOGFILE env var functionality
let stats = iroh04::get::run_ticket(
let stats = iroh::get::run_ticket(
ticket,
keylog,
MAX_CONCURRENT_DIALS,

View File

@@ -1208,6 +1208,9 @@ impl MimeMessage {
}
msg_type
} else if filename == "multi-device-sync.json" {
if !context.get_config_bool(Config::SyncMsgs).await? {
return Ok(());
}
let serialized = String::from_utf8_lossy(decoded_data)
.parse()
.unwrap_or_default();

View File

@@ -114,7 +114,7 @@ pub enum Qr {
/// information to connect to and authenticate a backup provider.
///
/// The format is somewhat opaque, but `sendme` can deserialise this.
ticket: iroh04::provider::Ticket,
ticket: iroh::provider::Ticket,
},
/// Ask the user if they want to use the given service for video chats.
@@ -497,12 +497,12 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
/// Decodes a [`DCBACKUP_SCHEME`] QR code.
///
/// The format of this scheme is `DCBACKUP:<encoded ticket>`. The encoding is the
/// [`iroh04::provider::Ticket`]'s `Display` impl.
/// [`iroh::provider::Ticket`]'s `Display` impl.
fn decode_backup(qr: &str) -> Result<Qr> {
let payload = qr
.strip_prefix(DCBACKUP_SCHEME)
.ok_or_else(|| anyhow!("invalid DCBACKUP scheme"))?;
let ticket: iroh04::provider::Ticket = payload.parse().context("invalid DCBACKUP payload")?;
let ticket: iroh::provider::Ticket = payload.parse().context("invalid DCBACKUP payload")?;
Ok(Qr::Backup { ticket })
}

View File

@@ -23,6 +23,7 @@ use crate::ephemeral::{stock_ephemeral_timer_changed, Timer as EphemeralTimer};
use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::imap::{markseen_on_imap_table, GENERATED_PREFIX};
use crate::key::DcKey;
use crate::location;
use crate::log::LogExt;
use crate::message::{
@@ -601,11 +602,7 @@ async fn add_parts(
context,
mime_parser,
is_partial_download.is_some(),
if test_normal_chat.is_none() {
allow_creation
} else {
true
},
test_normal_chat.is_some() || allow_creation,
create_blocked,
from_id,
to_ids,
@@ -617,6 +614,7 @@ async fn add_parts(
}
}
loop {
// if the chat is somehow blocked but we want to create a non-blocked chat,
// unblock the chat
if chat_id_blocked != Blocked::Not && create_blocked != Blocked::Yes {
@@ -705,7 +703,7 @@ async fn add_parts(
}
};
if let Some(chat) = test_normal_chat {
if let Some(chat) = test_normal_chat.as_ref() {
chat_id = Some(chat.id);
chat_id_blocked = chat.blocked;
} else if allow_creation {
@@ -719,7 +717,8 @@ async fn add_parts(
}
}
if let Some(chat_id) = chat_id {
let chat_id_ref = &mut chat_id;
if let Some(chat_id) = *chat_id_ref {
if chat_id_blocked != Blocked::Not {
if chat_id_blocked != create_blocked {
chat_id.set_blocked(context, create_blocked).await?;
@@ -766,7 +765,44 @@ async fn add_parts(
// That's why the config is checked here, and not above.
&& context.get_config_bool(Config::VerifiedOneOnOneChats).await?
{
new_protection = ProtectionStatus::ProtectionBroken;
let decryption_info = &mime_parser.decryption_info;
new_protection =
match decryption_info.autocrypt_header.as_ref().filter(|ah| {
Some(&ah.public_key.fingerprint())
!= decryption_info
.peerstate
.as_ref()
.and_then(|p| p.verified_key_fingerprint.as_ref())
}) {
None => {
if let Some(new_chat_id) = create_adhoc_group(
context,
mime_parser,
create_blocked,
&[ContactId::SELF, from_id],
)
.await
.context("could not create ad hoc group")?
{
info!(
context,
"Moving message to a new ad-hoc group keeping 1:1 chat \
protection.",
);
*chat_id_ref = Some(new_chat_id);
chat_id_blocked = create_blocked;
continue;
} else {
warn!(
context,
"Rejected to create an ad-hoc group, but keeping 1:1 chat \
protection.",
);
}
chat.protected
}
Some(_) => ProtectionStatus::ProtectionBroken,
};
}
if chat.protected != new_protection {
// The message itself will be sorted under the device message since the device
@@ -795,6 +831,8 @@ async fn add_parts(
} else {
MessageState::InFresh
};
break;
}
} else {
// Outgoing
@@ -1481,7 +1519,16 @@ async fn lookup_chat_by_reply(
// If this was a private message just to self, it was probably a private reply.
// It should not go into the group then, but into the private chat.
if is_probably_private_reply(context, to_ids, from_id, mime_parser, parent_chat.id).await? {
if is_probably_private_reply(
context,
to_ids,
from_id,
mime_parser,
parent_chat.id,
&parent_chat.grpid,
)
.await?
{
return Ok(None);
}
@@ -1511,13 +1558,14 @@ async fn is_probably_private_reply(
from_id: ContactId,
mime_parser: &MimeMessage,
parent_chat_id: ChatId,
parent_chat_grpid: &str,
) -> Result<bool> {
// Usually we don't want to show private replies in the parent chat, but in the
// 1:1 chat with the sender.
//
// There is one exception: Classical MUA replies to two-member groups
// should be assigned to the group chat. We restrict this exception to classical emails, as chat-group-messages
// contain a Chat-Group-Id header and can be sorted into the correct chat this way.
// An exception is replies to 2-member groups from classical MUAs or to 2-member ad-hoc
// groups. Such messages can't contain a Chat-Group-Id header and need to be sorted purely by
// References/In-Reply-To.
let private_message =
(to_ids == [ContactId::SELF]) || (from_id == ContactId::SELF && to_ids.len() == 1);
@@ -1525,7 +1573,7 @@ async fn is_probably_private_reply(
return Ok(false);
}
if !mime_parser.has_chat_version() {
if !mime_parser.has_chat_version() || parent_chat_grpid.is_empty() {
let chat_contacts = chat::get_chat_contacts(context, parent_chat_id).await?;
if chat_contacts.len() == 2 && chat_contacts.contains(&ContactId::SELF) {
return Ok(false);
@@ -1560,6 +1608,10 @@ async fn create_or_lookup_group(
member_ids.push(ContactId::SELF);
}
if member_ids.len() < 3 {
info!(context, "Not creating ad-hoc group: too few contacts.");
return Ok(None);
}
let res = create_adhoc_group(context, mime_parser, create_blocked, &member_ids)
.await
.context("could not create ad hoc group")?
@@ -1584,7 +1636,8 @@ async fn create_or_lookup_group(
// they belong to the group because of the Chat-Group-Id or Message-Id header
if let Some(chat_id) = chat_id {
if !mime_parser.has_chat_version()
&& is_probably_private_reply(context, to_ids, from_id, mime_parser, chat_id).await?
&& is_probably_private_reply(context, to_ids, from_id, mime_parser, chat_id, &grpid)
.await?
{
return Ok(None);
}
@@ -2213,11 +2266,6 @@ async fn create_adhoc_group(
return Ok(None);
}
if member_ids.len() < 3 {
info!(context, "Not creating ad-hoc group: too few contacts.");
return Ok(None);
}
// use subject as initial chat name
let grpname = mime_parser
.get_subject()

View File

@@ -43,13 +43,6 @@ pub(crate) struct SyncItems {
}
impl Context {
/// Checks if sync messages shall be sent.
/// Receiving sync messages is currently always enabled;
/// the messages are force-encrypted anyway.
async fn is_sync_sending_enabled(&self) -> Result<bool> {
self.get_config_bool(Config::SendSyncMsgs).await
}
/// Adds an item to the list of items that should be synchronized to other devices.
pub(crate) async fn add_sync_item(&self, data: SyncData) -> Result<()> {
self.add_sync_item_with_timestamp(data, time()).await
@@ -58,7 +51,7 @@ impl Context {
/// Adds item and timestamp to the list of items that should be synchronized to other devices.
/// If device synchronization is disabled, the function does nothing.
async fn add_sync_item_with_timestamp(&self, data: SyncData, timestamp: i64) -> Result<()> {
if !self.is_sync_sending_enabled().await? {
if !self.get_config_bool(Config::SyncMsgs).await? {
return Ok(());
}
@@ -75,7 +68,7 @@ impl Context {
/// If device synchronization is disabled,
/// no tokens exist or the chat is unpromoted, the function does nothing.
pub(crate) async fn sync_qr_code_tokens(&self, chat_id: Option<ChatId>) -> Result<()> {
if !self.is_sync_sending_enabled().await? {
if !self.get_config_bool(Config::SyncMsgs).await? {
return Ok(());
}
@@ -267,20 +260,20 @@ mod tests {
use crate::token::Namespace;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_is_sync_sending_enabled() -> Result<()> {
async fn test_config_sync_msgs() -> Result<()> {
let t = TestContext::new_alice().await;
assert!(!t.is_sync_sending_enabled().await?);
t.set_config_bool(Config::SendSyncMsgs, true).await?;
assert!(t.is_sync_sending_enabled().await?);
t.set_config_bool(Config::SendSyncMsgs, false).await?;
assert!(!t.is_sync_sending_enabled().await?);
assert!(!t.get_config_bool(Config::SyncMsgs).await?);
t.set_config_bool(Config::SyncMsgs, true).await?;
assert!(t.get_config_bool(Config::SyncMsgs).await?);
t.set_config_bool(Config::SyncMsgs, false).await?;
assert!(!t.get_config_bool(Config::SyncMsgs).await?);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_build_sync_json() -> Result<()> {
let t = TestContext::new_alice().await;
t.set_config_bool(Config::SendSyncMsgs, true).await?;
t.set_config_bool(Config::SyncMsgs, true).await?;
assert!(t.build_sync_json().await?.is_none());
@@ -325,7 +318,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_build_sync_json_sync_msgs_off() -> Result<()> {
let t = TestContext::new_alice().await;
t.set_config_bool(Config::SendSyncMsgs, false).await?;
t.set_config_bool(Config::SyncMsgs, false).await?;
t.add_sync_item(SyncData::AddQrToken(QrTokenData {
invitenumber: "testinvite".to_string(),
auth: "testauth".to_string(),
@@ -453,7 +446,7 @@ mod tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_send_sync_msg() -> Result<()> {
let alice = TestContext::new_alice().await;
alice.set_config_bool(Config::SendSyncMsgs, true).await?;
alice.set_config_bool(Config::SyncMsgs, true).await?;
alice
.add_sync_item(SyncData::AddQrToken(QrTokenData {
invitenumber: "in".to_string(),
@@ -480,6 +473,7 @@ mod tests {
// also here, self-talk should stay hidden
let sent_msg = alice.pop_sent_msg().await;
let alice2 = TestContext::new_alice().await;
alice2.set_config_bool(Config::SyncMsgs, true).await?;
alice2.recv_msg(&sent_msg).await;
assert!(token::exists(&alice2, token::Namespace::Auth, "testtoken").await);
assert_eq!(Chatlist::try_load(&alice2, 0, None, None).await?.len(), 0);

View File

@@ -4,7 +4,7 @@ use pretty_assertions::assert_eq;
use crate::chat::{Chat, ProtectionStatus};
use crate::chatlist::Chatlist;
use crate::config::Config;
use crate::constants::DC_GCL_FOR_FORWARDING;
use crate::constants::{Chattype, DC_GCL_FOR_FORWARDING};
use crate::contact::VerifiedStatus;
use crate::contact::{Contact, Origin};
use crate::message::{Message, Viewtype};
@@ -12,7 +12,9 @@ use crate::mimefactory::MimeFactory;
use crate::mimeparser::SystemMessage;
use crate::receive_imf::receive_imf;
use crate::stock_str;
use crate::test_utils::{get_chat_msg, mark_as_verified, TestContext, TestContextManager};
use crate::test_utils::{
get_chat_msg, mark_as_verified, TestContext, TestContextManager,
};
use crate::{e2ee, message};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
@@ -742,6 +744,50 @@ async fn test_create_oneonone_chat_with_former_verified_contact() -> Result<()>
Ok(())
}
/// Some messages are sent unencrypted, but they mustn't break a verified chat protection.
/// They must go to a new 2-member ad-hoc group instead.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_unencrypted() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
enable_verified_oneonone_chats(&[&alice]).await;
mark_as_verified(&alice, &bob).await;
alice.create_chat(&bob).await;
let msg = tcm.send_recv(&bob, &alice, "hi").await;
assert!(!msg.get_showpadlock());
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
// The next unencrypted message must get to the same ad-hoc group thanks to "In-Reply-To".
let msg = tcm.send_recv(&bob, &alice, "hi again").await;
assert!(!msg.get_showpadlock());
assert_eq!(msg.chat_id, alice_chat.id);
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
// This message is missed by Alice.
let chat_id = bob.get_chat(&alice).await.id;
bob.send_text(chat_id, "hi to the void").await;
// But the next message must get to the same ad-hoc group thanks to "References".
let msg = tcm.send_recv(&bob, &alice, "hi in a new group").await;
assert!(!msg.get_showpadlock());
assert_eq!(msg.chat_id, alice_chat.id);
let alice_chat = Chat::load_from_db(&alice, msg.chat_id).await?;
assert_eq!(alice_chat.typ, Chattype::Group);
let alice_chat = alice.get_chat(&bob).await;
assert!(alice_chat.is_protected());
assert!(!alice_chat.is_protection_broken());
alice
.golden_test_chat(alice_chat.id, "verified_chats_test_unencrypted")
.await;
Ok(())
}
// ============== Helper Functions ==============
async fn assert_verified(this: &TestContext, other: &TestContext, protected: ProtectionStatus) {

View File

@@ -724,18 +724,18 @@ mod tests {
let raw = include_bytes!("../test-data/message/wrong-html.eml");
let expected =
"Hop: From: oxbsltgw18.schlund.de; By: mrelayeu.kundenserver.de; Date: Thu, 06 Aug 2020 16:40:31 +0000\n\
Hop: From: mout.kundenserver.de; By: dd37930.kasserver.com; Date: Thu, 06 Aug 2020 16:40:32 +0000";
"Hop: From: oxbsltgw18.schlund.de; By: mrelayeu.kundenserver.de; Date: Thu, 6 Aug 2020 16:40:31 +0000\n\
Hop: From: mout.kundenserver.de; By: dd37930.kasserver.com; Date: Thu, 6 Aug 2020 16:40:32 +0000";
check_parse_receive_headers(raw, expected);
let raw = include_bytes!("../test-data/message/posteo_ndn.eml");
let expected =
"Hop: By: mout01.posteo.de; Date: Tue, 09 Jun 2020 18:44:22 +0000\n\
Hop: From: mout01.posteo.de; By: mx04.posteo.de; Date: Tue, 09 Jun 2020 18:44:22 +0000\n\
Hop: From: mx04.posteo.de; By: mailin06.posteo.de; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
Hop: From: mailin06.posteo.de; By: proxy02.posteo.de; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
Hop: From: proxy02.posteo.de; By: proxy02.posteo.name; Date: Tue, 09 Jun 2020 18:44:23 +0000\n\
Hop: From: proxy02.posteo.name; By: dovecot03.posteo.local; Date: Tue, 09 Jun 2020 18:44:24 +0000";
"Hop: By: mout01.posteo.de; Date: Tue, 9 Jun 2020 18:44:22 +0000\n\
Hop: From: mout01.posteo.de; By: mx04.posteo.de; Date: Tue, 9 Jun 2020 18:44:22 +0000\n\
Hop: From: mx04.posteo.de; By: mailin06.posteo.de; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
Hop: From: mailin06.posteo.de; By: proxy02.posteo.de; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
Hop: From: proxy02.posteo.de; By: proxy02.posteo.name; Date: Tue, 9 Jun 2020 18:44:23 +0000\n\
Hop: From: proxy02.posteo.name; By: dovecot03.posteo.local; Date: Tue, 9 Jun 2020 18:44:24 +0000";
check_parse_receive_headers(raw, expected);
}

View File

@@ -0,0 +1,4 @@
Single#Chat#10: bob@example.net [bob@example.net] 🛡️
--------------------------------------------------------------------------------
Msg#10: info (Contact#Contact#Info): Messages are guaranteed to be end-to-end encrypted from now on. [NOTICED][INFO 🛡️]
--------------------------------------------------------------------------------