From 6fdbb6505e40b275a30f59a6541e8cc76d1509f3 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Thu, 28 Dec 2023 18:37:28 +0100 Subject: [PATCH 1/6] Add self-reporting API --- deltachat-jsonrpc/src/api.rs | 5 ++++ src/context.rs | 48 ++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index e2bf6c62f9..fa0f6bed70 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -325,6 +325,11 @@ impl CommandApi { ctx.get_info().await } + async fn draft_self_report(&self, account_id: u32) -> Result { + let ctx = self.get_context(account_id).await?; + Ok(ctx.draft_self_report().await?.to_u32()) + } + /// Sets the given configuration key. async fn set_config(&self, account_id: u32, key: String, value: Option) -> Result<()> { let ctx = self.get_context(account_id).await?; diff --git a/src/context.rs b/src/context.rs index 38bf11e0d8..0e9b0e1fbb 100644 --- a/src/context.rs +++ b/src/context.rs @@ -15,14 +15,16 @@ use tokio::sync::{Mutex, Notify, RwLock}; use crate::chat::{get_chat_cnt, ChatId}; use crate::config::Config; -use crate::constants::{DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_VERSION_STR}; +use crate::constants::{ + DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR, +}; use crate::contact::Contact; use crate::debug_logging::DebugLogging; use crate::events::{Event, EventEmitter, EventType, Events}; use crate::imap::{FolderMeaning, Imap, ServerMetadata}; use crate::key::{load_self_public_key, DcKey as _}; use crate::login_param::LoginParam; -use crate::message::{self, MessageState, MsgId}; +use crate::message::{self, Message, MessageState, MsgId, Viewtype}; use crate::quota::QuotaInfo; use crate::scheduler::{convert_folder_meaning, SchedulerState}; use crate::sql::Sql; @@ -859,6 +861,48 @@ impl Context { Ok(res) } + async fn get_self_report(&self) -> Result { + let mut res = String::new(); + res += &format!("core_version {}\n", get_version_str()); + + let num_msgs: u32 = self + .sql + .query_get_value( + "SELECT COUNT(*) FROM msgs WHERE hidden=0 AND chat_id!=?", + (DC_CHAT_ID_TRASH,), + ) + .await? + .unwrap_or_default(); + res += &format!("num_msgs {}\n", num_msgs); + + let num_chats: u32 = self + .sql + .query_get_value("SELECT COUNT(*) FROM chats WHERE id>9 AND blocked!=1", ()) + .await? + .unwrap_or_default(); + res += &format!("num_chats {}\n", num_chats); + + let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len(); + res += &format!("db_size_bytes {}\n", db_size); + + Ok(res) + } + + /// TODO doc comment + pub async fn draft_self_report(&self) -> Result { + const SELF_REPORTING_BOT: &str = "self_reporting@testrun.org"; + + let contact = Contact::create(self, "Statistics bot", SELF_REPORTING_BOT).await?; + let chat_id = ChatId::create_for_contact(self, contact).await?; + + let mut msg = Message::new(Viewtype::Text); + msg.text = self.get_self_report().await?; + + chat_id.set_draft(self, Some(&mut msg)).await?; + + Ok(chat_id) + } + /// Get a list of fresh, unmuted messages in unblocked chats. /// /// The list starts with the most recent message From f3e2e5d9dcf4d855e43345b08bc3cd7ba0d36ba8 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 8 Jan 2024 11:52:58 +0100 Subject: [PATCH 2/6] Add secret_key field --- src/context.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/context.rs b/src/context.rs index 0e9b0e1fbb..c8c37fae27 100644 --- a/src/context.rs +++ b/src/context.rs @@ -22,7 +22,7 @@ use crate::contact::Contact; use crate::debug_logging::DebugLogging; use crate::events::{Event, EventEmitter, EventType, Events}; use crate::imap::{FolderMeaning, Imap, ServerMetadata}; -use crate::key::{load_self_public_key, DcKey as _}; +use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _}; use crate::login_param::LoginParam; use crate::message::{self, Message, MessageState, MsgId, Viewtype}; use crate::quota::QuotaInfo; @@ -885,6 +885,10 @@ impl Context { let db_size = tokio::fs::metadata(&self.sql.dbfile).await?.len(); res += &format!("db_size_bytes {}\n", db_size); + let secret_key = &load_self_secret_key(self).await?.primary_key; + let key_created = secret_key.created_at().timestamp(); + res += &format!("key_created {}", key_created); + Ok(res) } From 0c211491b369859407508f63dd3e137b3fad9ca1 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Sun, 21 Jan 2024 14:42:52 +0100 Subject: [PATCH 3/6] Add self_reporting_id --- src/config.rs | 4 ++++ src/context.rs | 20 +++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/config.rs b/src/config.rs index 0594939e38..5b26a3193f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -347,6 +347,10 @@ pub enum Config { /// Row ID of the key in the `keypairs` table /// used for signatures, encryption to self and included in `Autocrypt` header. KeyId, + + /// This key is sent to the self_reporting bot so that the bot can recognize the user + /// without storing the email address + SelfReportingId, } impl Config { diff --git a/src/context.rs b/src/context.rs index c8c37fae27..5b9f9b3299 100644 --- a/src/context.rs +++ b/src/context.rs @@ -30,7 +30,7 @@ use crate::scheduler::{convert_folder_meaning, SchedulerState}; use crate::sql::Sql; use crate::stock_str::StockStrings; use crate::timesmearing::SmearedTimestamp; -use crate::tools::{duration_to_str, time}; +use crate::tools::{create_id, duration_to_str, time}; /// Builder for the [`Context`]. /// @@ -887,12 +887,26 @@ impl Context { let secret_key = &load_self_secret_key(self).await?.primary_key; let key_created = secret_key.created_at().timestamp(); - res += &format!("key_created {}", key_created); + res += &format!("key_created {}\n", key_created); + + let self_reporting_id = match self.get_config(Config::SelfReportingId).await? { + Some(id) => id, + None => { + let id = create_id(); + self.set_config(Config::SelfReportingId, Some(&id)).await?; + id + } + }; + res += &format!("self_reporting_id {}\n", self_reporting_id); Ok(res) } - /// TODO doc comment + /// Drafts a message with statistics about the usage of Delta Chat. + /// The user can inspect the message if they want, and then hit "Send". + /// + /// On the other end, a bot will receive the message and make it available + /// to Delta Chat's developers. pub async fn draft_self_report(&self) -> Result { const SELF_REPORTING_BOT: &str = "self_reporting@testrun.org"; From ae711db5437072cf4d0ce767206f8c162b175d2d Mon Sep 17 00:00:00 2001 From: Hocuri Date: Mon, 29 Jan 2024 18:50:59 +0100 Subject: [PATCH 4/6] Include the bot's public key in Delta Chat --- src/context.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++---- src/peerstate.rs | 27 ++++++++++++++++----- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/context.rs b/src/context.rs index 5b9f9b3299..e880390b7b 100644 --- a/src/context.rs +++ b/src/context.rs @@ -10,10 +10,12 @@ use std::time::{Duration, Instant, SystemTime}; use anyhow::{bail, ensure, Context as _, Result}; use async_channel::{self as channel, Receiver, Sender}; +use pgp::SignedPublicKey; use ratelimit::Ratelimit; use tokio::sync::{Mutex, Notify, RwLock}; -use crate::chat::{get_chat_cnt, ChatId}; +use crate::aheader::EncryptPreference; +use crate::chat::{get_chat_cnt, ChatId, ProtectionStatus}; use crate::config::Config; use crate::constants::{ DC_BACKGROUND_FETCH_QUOTA_CHECK_RATELIMIT, DC_CHAT_ID_TRASH, DC_VERSION_STR, @@ -25,6 +27,7 @@ use crate::imap::{FolderMeaning, Imap, ServerMetadata}; use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _}; use crate::login_param::LoginParam; use crate::message::{self, Message, MessageState, MsgId, Viewtype}; +use crate::peerstate::{Peerstate, PeerstateKeyType}; use crate::quota::QuotaInfo; use crate::scheduler::{convert_folder_meaning, SchedulerState}; use crate::sql::Sql; @@ -897,7 +900,7 @@ impl Context { id } }; - res += &format!("self_reporting_id {}\n", self_reporting_id); + res += &format!("self_reporting_id {}", self_reporting_id); Ok(res) } @@ -910,8 +913,36 @@ impl Context { pub async fn draft_self_report(&self) -> Result { const SELF_REPORTING_BOT: &str = "self_reporting@testrun.org"; - let contact = Contact::create(self, "Statistics bot", SELF_REPORTING_BOT).await?; - let chat_id = ChatId::create_for_contact(self, contact).await?; + let contact_id = Contact::create(self, "Statistics bot", SELF_REPORTING_BOT).await?; + let chat_id = ChatId::create_for_contact(self, contact_id).await?; + + // We're including the bot's public key in Delta Chat + // so that the first message to the bot can directly be encrypted: + let public_key = SignedPublicKey::from_base64( + "xjMEZbfBlBYJKwYBBAHaRw8BAQdABpLWS2PUIGGo4pslVt4R8sylP5wZihmhf1DTDr3oCM\ + PNHDxzZWxmX3JlcG9ydGluZ0B0ZXN0cnVuLm9yZz7CiwQQFggAMwIZAQUCZbfBlAIbAwQLCQgHBhUI\ + CQoLAgMWAgEWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohD8dAQCQV7CoH6UP4PD+Nq\ + I4kW5tbbqdh2AnDROg60qotmLExAEAxDfd3QHAK9f8b9qQUbLmHIztCLxhEuVbWPBEYeVW0gvOOARl\ + t8GUEgorBgEEAZdVAQUBAQdAMBUhYoAAcI625vGZqnM5maPX4sGJ7qvJxPAFILPy6AcDAQgHwngEGB\ + YIACAFAmW3wZQCGwwWIQTS2i16sHeYTckGn284K3M5Z4oohAAKCRA4K3M5Z4oohPwCAQCvzk1ObIkj\ + 2GqsuIfaULlgdnfdZY8LNary425CEfHZDQD5AblXVrlMO1frdlc/Vo9z3pEeCrfYdD7ITD3/OeVoiQ\ + 4=", + )?; + let mut peerstate = Peerstate::from_public_key( + SELF_REPORTING_BOT, + 0, + EncryptPreference::Mutual, + &public_key, + ); + peerstate.set_verified( + PeerstateKeyType::PublicKey, + public_key.fingerprint(), + "".to_string(), + )?; + peerstate.save_to_db(&self.sql).await?; + chat_id + .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id)) + .await?; let mut msg = Message::new(Viewtype::Text); msg.text = self.get_self_report().await?; @@ -1191,8 +1222,9 @@ mod tests { use crate::constants::Chattype; use crate::contact::ContactId; use crate::message::{Message, Viewtype}; + use crate::mimeparser::SystemMessage; use crate::receive_imf::receive_imf; - use crate::test_utils::TestContext; + use crate::test_utils::{get_chat_msg, TestContext}; use crate::tools::create_outgoing_rfc724_mid; #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -1731,4 +1763,24 @@ mod tests { Ok(()) } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_draft_self_report() -> Result<()> { + let alice = TestContext::new_alice().await; + + let chat_id = alice.draft_self_report().await?; + let msg = get_chat_msg(&alice, chat_id, 0, 1).await; + assert_eq!(msg.get_info_type(), SystemMessage::ChatProtectionEnabled); + + let chat = Chat::load_from_db(&alice, chat_id).await?; + assert!(chat.is_protected()); + + let mut draft = chat_id.get_draft(&alice).await?.unwrap(); + assert!(draft.text.starts_with("core_version")); + + // Test that sending into the protected chat works: + let sent = alice.send_msg(chat_id, &mut draft).await; + + Ok(()) + } } diff --git a/src/peerstate.rs b/src/peerstate.rs index a8b9909474..fc3f39e15b 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -97,13 +97,28 @@ pub struct Peerstate { impl Peerstate { /// Creates a peerstate from the `Autocrypt` header. pub fn from_header(header: &Aheader, message_time: i64) -> Self { + Self::from_public_key( + &header.addr, + message_time, + header.prefer_encrypt, + &header.public_key, + ) + } + + /// Creates a peerstate from the given public key. + pub fn from_public_key( + addr: &str, + last_seen: i64, + prefer_encrypt: EncryptPreference, + public_key: &SignedPublicKey, + ) -> Self { Peerstate { - addr: header.addr.clone(), - last_seen: message_time, - last_seen_autocrypt: message_time, - prefer_encrypt: header.prefer_encrypt, - public_key: Some(header.public_key.clone()), - public_key_fingerprint: Some(header.public_key.fingerprint()), + addr: addr.to_string(), + last_seen, + last_seen_autocrypt: last_seen, + prefer_encrypt, + public_key: Some(public_key.clone()), + public_key_fingerprint: Some(public_key.fingerprint()), gossip_key: None, gossip_key_fingerprint: None, gossip_timestamp: 0, From b4f4c18d50c6de8c30a6fb74515cc83a0e1c41eb Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 6 Feb 2024 23:19:55 +0100 Subject: [PATCH 5/6] adapt to changed set_verified() api --- src/context.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/context.rs b/src/context.rs index e880390b7b..912bc6bc65 100644 --- a/src/context.rs +++ b/src/context.rs @@ -27,7 +27,7 @@ use crate::imap::{FolderMeaning, Imap, ServerMetadata}; use crate::key::{load_self_public_key, load_self_secret_key, DcKey as _}; use crate::login_param::LoginParam; use crate::message::{self, Message, MessageState, MsgId, Viewtype}; -use crate::peerstate::{Peerstate, PeerstateKeyType}; +use crate::peerstate::Peerstate; use crate::quota::QuotaInfo; use crate::scheduler::{convert_folder_meaning, SchedulerState}; use crate::sql::Sql; @@ -934,11 +934,8 @@ impl Context { EncryptPreference::Mutual, &public_key, ); - peerstate.set_verified( - PeerstateKeyType::PublicKey, - public_key.fingerprint(), - "".to_string(), - )?; + let fingerprint = public_key.fingerprint(); + peerstate.set_verified(public_key, fingerprint, "".to_string())?; peerstate.save_to_db(&self.sql).await?; chat_id .set_protection(self, ProtectionStatus::Protected, time(), Some(contact_id)) From 8edc1b70686c99f7e7006757a2e3e16023ab47c3 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 6 Feb 2024 23:20:04 +0100 Subject: [PATCH 6/6] fix some warnings --- src/context.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/context.rs b/src/context.rs index 912bc6bc65..a52097ab97 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1460,6 +1460,7 @@ mod tests { "mail_security", "notify_about_wrong_pw", "save_mime_headers", + "self_reporting_id", "selfstatus", "send_server", "send_user", @@ -1776,7 +1777,7 @@ mod tests { assert!(draft.text.starts_with("core_version")); // Test that sending into the protected chat works: - let sent = alice.send_msg(chat_id, &mut draft).await; + let _sent = alice.send_msg(chat_id, &mut draft).await; Ok(()) }