From d05dd977d9168744f364265713516b4197f22f31 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 1 Oct 2020 17:05:58 +0200 Subject: [PATCH 01/23] migrate database add 'protected' row in chats table, convert old verified-groups to 'protected' --- src/sql.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sql.rs b/src/sql.rs index 98fc7d21e..8006022d8 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -1368,6 +1368,20 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label); .await?; sql.set_raw_config_int(context, "dbversion", 68).await?; } + if dbversion < 69 { + info!(context, "[migration] v69"); + sql.execute( + "ALTER TABLE chats ADD COLUMN protected INTEGER DEFAULT 0;", + paramsv![], + ) + .await?; + sql.execute( + "UPDATE chats SET protected=1, type=120 WHERE type=130;", // 120=group, 130=old verified group + paramsv![], + ) + .await?; + sql.set_raw_config_int(context, "dbversion", 69).await?; + } // (2) updates that require high-level objects // (the structure is complete now and all objects are usable) From ab8bf3c2f30f2cbb7581a7242dddc0f872b3236b Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 1 Oct 2020 18:36:13 +0200 Subject: [PATCH 02/23] use ProtectionStatus to create chats --- deltachat-ffi/deltachat.h | 6 ++--- deltachat-ffi/src/lib.rs | 8 +++---- examples/repl/cmdline.rs | 13 ++++++----- src/chat.rs | 49 +++++++++++++++++++++++++++++---------- src/chatlist.rs | 10 ++++---- src/dc_receive_imf.rs | 25 +++++++++----------- src/securejoin.rs | 3 ++- 7 files changed, 69 insertions(+), 45 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index a520e3c13..434c7bc2d 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1299,15 +1299,15 @@ dc_chat_t* dc_get_chat (dc_context_t* context, uint32_t ch * * @memberof dc_context_t * @param context The context object. - * @param verified If set to 1 the function creates a secure verified group. - * Only secure-verified members are allowed in these groups + * @param protect If set to 1 the function creates group with protection initially enabled. + * Only verified members are allowed in these groups * and end-to-end-encryption is always enabled. * @param name The name of the group chat to create. * The name may be changed later using dc_set_chat_name(). * To find out the name of a group later, see dc_chat_get_name() * @return The chat ID of the new group chat, 0 on errors. */ -uint32_t dc_create_group_chat (dc_context_t* context, int verified, const char* name); +uint32_t dc_create_group_chat (dc_context_t* context, int protect, const char* name); /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 52d779baa..bf83536f8 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -25,7 +25,7 @@ use async_std::task::{block_on, spawn}; use num_traits::{FromPrimitive, ToPrimitive}; use deltachat::accounts::Accounts; -use deltachat::chat::{ChatId, ChatVisibility, MuteDuration}; +use deltachat::chat::{ChatId, ChatVisibility, MuteDuration, ProtectionStatus}; use deltachat::constants::DC_MSG_ID_LAST_SPECIAL; use deltachat::contact::{Contact, Origin}; use deltachat::context::Context; @@ -1170,7 +1170,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) - #[no_mangle] pub unsafe extern "C" fn dc_create_group_chat( context: *mut dc_context_t, - verified: libc::c_int, + protect: libc::c_int, name: *const libc::c_char, ) -> u32 { if context.is_null() || name.is_null() { @@ -1178,14 +1178,14 @@ pub unsafe extern "C" fn dc_create_group_chat( return 0; } let ctx = &*context; - let verified = if let Some(s) = contact::VerifiedStatus::from_i32(verified) { + let protect = if let Some(s) = contact::ProtectionStatus::from_i32(protect) { s } else { return 0; }; block_on(async move { - chat::create_group_chat(&ctx, verified, to_string_lossy(name)) + chat::create_group_chat(&ctx, protect, to_string_lossy(name)) .await .log_err(ctx, "Failed to create group chat") .map(|id| id.to_u32()) diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index b4f172e22..a3e49db36 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use anyhow::{bail, ensure}; use async_std::path::Path; -use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility}; +use deltachat::chat::{self, Chat, ChatId, ChatItem, ChatVisibility, ProtectionStatus}; use deltachat::chatlist::*; use deltachat::constants::*; use deltachat::contact::*; @@ -357,7 +357,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu createchat \n\ createchatbymsg \n\ creategroup \n\ - createverified \n\ + createprotected \n\ addmember \n\ removemember \n\ groupname \n\ @@ -654,15 +654,16 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu "creategroup" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = - chat::create_group_chat(&context, VerifiedStatus::Unverified, arg1).await?; + chat::create_group_chat(&context, ProtectionStatus::Unprotected, arg1).await?; println!("Group#{} created successfully.", chat_id); } - "createverified" => { + "createprotected" => { ensure!(!arg1.is_empty(), "Argument missing."); - let chat_id = chat::create_group_chat(&context, VerifiedStatus::Verified, arg1).await?; + let chat_id = + chat::create_group_chat(&context, ProtectionStatus::Protected, arg1).await?; - println!("VerifiedGroup#{} created successfully.", chat_id); + println!("Group#{} created and protected successfully.", chat_id); } "addmember" => { ensure!(sel_chat.is_some(), "No chat selected"); diff --git a/src/chat.rs b/src/chat.rs index 42f5e585b..731ed9220 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1,5 +1,6 @@ //! # Chat module +use deltachat_derive::{FromSql, ToSql}; use std::convert::TryFrom; use std::time::{Duration, SystemTime}; @@ -45,6 +46,33 @@ pub enum ChatItem { }, } +#[derive( + Debug, + Display, + Clone, + Copy, + PartialEq, + Eq, + FromPrimitive, + ToPrimitive, + FromSql, + ToSql, + IntoStaticStr, + Serialize, + Deserialize, +)] +#[repr(u32)] +pub enum ProtectionStatus { + Unprotected = 0, + Protected = 1, +} + +impl Default for ProtectionStatus { + fn default() -> Self { + ProtectionStatus::Unprotected + } +} + /// Chat ID, including reserved IDs. /// /// Some chat IDs are reserved to identify special chat types. This @@ -1865,7 +1893,7 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec { pub async fn create_group_chat( context: &Context, - verified: VerifiedStatus, + protect: ProtectionStatus, chat_name: impl AsRef, ) -> Result { let chat_name = improve_single_line_input(chat_name); @@ -1877,16 +1905,13 @@ pub async fn create_group_chat( let grpid = dc_create_id(); context.sql.execute( - "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", + "INSERT INTO chats (type, name, grpid, param, created_timestamp, protected) VALUES(?, ?, ?, \'U=1\', ?, ?);", paramsv![ - if verified != VerifiedStatus::Unverified { - Chattype::VerifiedGroup - } else { - Chattype::Group - }, + Chattype::Group, chat_name, grpid, time(), + protect, ], ).await?; @@ -2898,7 +2923,7 @@ mod tests { async fn test_add_contact_to_chat_ex_add_self() { // Adding self to a contact should succeed, even though it's pointless. let t = TestContext::new().await; - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let chat_id = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false) @@ -3284,7 +3309,7 @@ mod tests { .await .unwrap(); async_std::task::sleep(std::time::Duration::from_millis(1000)).await; - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let chat_id3 = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); @@ -3329,7 +3354,7 @@ mod tests { #[async_std::test] async fn test_set_chat_name() { let t = TestContext::new().await; - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let chat_id = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); assert_eq!( @@ -3372,7 +3397,7 @@ mod tests { #[async_std::test] async fn test_shall_attach_selfavatar() { let t = TestContext::new().await; - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let chat_id = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap()); @@ -3396,7 +3421,7 @@ mod tests { #[async_std::test] async fn test_set_mute_duration() { let t = TestContext::new().await; - let chat_id = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let chat_id = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); // Initial diff --git a/src/chatlist.rs b/src/chatlist.rs index fbee5e1ed..237540d6c 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -440,13 +440,13 @@ mod tests { #[async_std::test] async fn test_try_load() { let t = TestContext::new().await; - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + let chat_id1 = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "a chat") .await .unwrap(); - let chat_id2 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "b chat") + let chat_id2 = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "b chat") .await .unwrap(); - let chat_id3 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "c chat") + let chat_id3 = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "c chat") .await .unwrap(); @@ -489,7 +489,7 @@ mod tests { async fn test_sort_self_talk_up_on_forward() { let t = TestContext::new().await; t.ctx.update_device_chats().await.unwrap(); - create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "a chat") .await .unwrap(); @@ -546,7 +546,7 @@ mod tests { #[async_std::test] async fn test_get_summary_unwrap() { let t = TestContext::new().await; - let chat_id1 = create_group_chat(&t.ctx, VerifiedStatus::Unverified, "a chat") + let chat_id1 = create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "a chat") .await .unwrap(); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index fc092bf39..29a4caf4d 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -4,7 +4,7 @@ use sha2::{Digest, Sha256}; use mailparse::SingleInfo; -use crate::chat::{self, Chat, ChatId}; +use crate::chat::{self, Chat, ChatId, ProtectionStatus}; use crate::config::Config; use crate::constants::*; use crate::contact::*; @@ -1199,7 +1199,7 @@ async fn create_or_lookup_group( || X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap())) { // group does not exist but should be created - let create_verified = if mime_parser.get(HeaderDef::ChatVerified).is_some() { + let create_protected = if mime_parser.get(HeaderDef::ChatVerified).is_some() { if let Err(err) = check_verified_properties(context, mime_parser, from_id as u32, to_ids).await { @@ -1207,9 +1207,9 @@ async fn create_or_lookup_group( let s = format!("{}. See 'Info' for more details", err); mime_parser.repl_msg_by_error(&s); } - VerifiedStatus::Verified + ProtectionStatus::Protected } else { - VerifiedStatus::Unverified + ProtectionStatus::Unprotected }; if !allow_creation { @@ -1222,7 +1222,7 @@ async fn create_or_lookup_group( &grpid, grpname.as_ref().unwrap(), create_blocked, - create_verified, + create_protected, ) .await; chat_id_blocked = create_blocked; @@ -1463,7 +1463,7 @@ async fn create_or_lookup_adhoc_group( &grpid, grpname, create_blocked, - VerifiedStatus::Unverified, + ProtectionStatus::Unprotected, ) .await; for &member_id in &member_ids { @@ -1480,20 +1480,17 @@ async fn create_group_record( grpid: impl AsRef, grpname: impl AsRef, create_blocked: Blocked, - create_verified: VerifiedStatus, + create_protected: ProtectionStatus, ) -> ChatId { if context.sql.execute( - "INSERT INTO chats (type, name, grpid, blocked, created_timestamp) VALUES(?, ?, ?, ?, ?);", + "INSERT INTO chats (type, name, grpid, blocked, created_timestamp, protected) VALUES(?, ?, ?, ?, ?, ?);", paramsv![ - if VerifiedStatus::Unverified != create_verified { - Chattype::VerifiedGroup - } else { - Chattype::Group - }, + Chattype::Group, grpname.as_ref(), grpid.as_ref(), create_blocked, time(), + create_protected, ], ).await .is_err() @@ -2182,7 +2179,7 @@ mod tests { assert!(one2one.get_visibility() == ChatVisibility::Archived); // create a group with bob, archive group - let group_id = chat::create_group_chat(&t.ctx, VerifiedStatus::Unverified, "foo") + let group_id = chat::create_group_chat(&t.ctx, ProtectionStatus::Unprotected, "foo") .await .unwrap(); chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await; diff --git a/src/securejoin.rs b/src/securejoin.rs index 1994fc7ad..a7299f827 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -1102,6 +1102,7 @@ mod tests { use super::*; use crate::chat; + use crate::chat::ProtectionStatus; use crate::peerstate::Peerstate; use crate::test_utils::TestContext; @@ -1328,7 +1329,7 @@ mod tests { let alice = TestContext::new_alice().await; let bob = TestContext::new_bob().await; - let chatid = chat::create_group_chat(&alice.ctx, VerifiedStatus::Verified, "the chat") + let chatid = chat::create_group_chat(&alice.ctx, ProtectionStatus::Protected, "the chat") .await .unwrap(); From b8a55f3aa4b58a75a7e79758ea877fb404096a46 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 1 Oct 2020 19:29:27 +0200 Subject: [PATCH 03/23] replace chat.is_verified() by chat.is_protected() --- deltachat-ffi/deltachat.h | 22 ++++++++++++---------- deltachat-ffi/src/lib.rs | 6 +++--- examples/repl/cmdline.rs | 10 ++++++++-- python/src/deltachat/chat.py | 4 ++-- python/tests/test_account.py | 4 ++-- src/chat.rs | 22 +++++++++++++--------- src/dc_receive_imf.rs | 2 +- src/securejoin.rs | 10 +++++----- 8 files changed, 46 insertions(+), 34 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 434c7bc2d..4a14d7d3a 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1329,7 +1329,7 @@ int dc_is_contact_in_chat (dc_context_t* context, uint32_t ch * If the group is already _promoted_ (any message was sent to the group), * all group members are informed by a special status message that is sent automatically by this function. * - * If the group is a verified group, only verified contacts can be added to the group. + * If the group has group protection enabled, only verified contacts can be added to the group. * * Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent. * @@ -1973,7 +1973,7 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char* * @param context The context object. * @param chat_id If set to a group-chat-id, * the Verified-Group-Invite protocol is offered in the QR code; - * works for verified groups as well as for normal groups. + * works for protected groups as well as for normal groups. * If set to 0, the Setup-Contact protocol is offered in the QR code. * See https://countermitm.readthedocs.io/en/latest/new.html * for details about both protocols. @@ -2003,7 +2003,7 @@ char* dc_get_securejoin_qr (dc_context_t* context, uint32_t ch * When the protocol has finished, an info-message is added to that chat. * - If the given QR code starts the Verified-Group-Invite protocol, * the function waits until the protocol has finished. - * This is because the verified group is not opportunistic + * This is because the protected group is not opportunistic * and can be created only when the contacts have verified each other. * * See https://countermitm.readthedocs.io/en/latest/new.html @@ -2015,8 +2015,8 @@ char* dc_get_securejoin_qr (dc_context_t* context, uint32_t ch * to dc_check_qr(). * @return Chat-id of the joined chat, the UI may redirect to the this chat. * If the out-of-band verification failed or was aborted, 0 is returned. - * A returned chat-id does not guarantee that the chat or the belonging contact is verified. - * If needed, this be checked with dc_chat_is_verified() and dc_contact_is_verified(), + * A returned chat-id does not guarantee that the chat is protected or the belonging contact is verified. + * If needed, this be checked with dc_chat_is_protected() and dc_contact_is_verified(), * however, in practise, the UI will just listen to #DC_EVENT_CONTACTS_CHANGED unconditionally. */ uint32_t dc_join_securejoin (dc_context_t* context, const char* qr); @@ -2942,15 +2942,17 @@ int dc_chat_can_send (const dc_chat_t* chat); /** - * Check if a chat is verified. Verified chats contain only verified members - * and encryption is alwasy enabled. Verified chats are created using - * dc_create_group_chat() by setting the 'verified' parameter to true. + * Check if a chat is protected. + * Protected chats contain only verified members and encryption is always enabled. + * Protected chats are created using dc_create_group_chat() by setting the 'protect' parameter to 1. + * The status can be changed using dc_set_chat_protection(). * * @memberof dc_chat_t * @param chat The chat object. - * @return 1=chat verified, 0=chat is not verified + * @return 1=chat protected, 0=chat is not protected */ -int dc_chat_is_verified (const dc_chat_t* chat); +int dc_chat_is_protected (const dc_chat_t* chat); +#define dc_chat_is_verified dc_chat_is_protected // allow using old function name for a while /** diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index bf83536f8..394b1bc81 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2389,13 +2389,13 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int { } #[no_mangle] -pub unsafe extern "C" fn dc_chat_is_verified(chat: *mut dc_chat_t) -> libc::c_int { +pub unsafe extern "C" fn dc_chat_is_protected(chat: *mut dc_chat_t) -> libc::c_int { if chat.is_null() { - eprintln!("ignoring careless call to dc_chat_is_verified()"); + eprintln!("ignoring careless call to dc_chat_is_protected()"); return 0; } let ffi_chat = &*chat; - ffi_chat.chat.is_verified() as libc::c_int + ffi_chat.chat.is_protected() as libc::c_int } #[no_mangle] diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index a3e49db36..9682a7c55 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -523,7 +523,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu for i in (0..cnt).rev() { let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?; println!( - "{}#{}: {} [{} fresh] {}", + "{}#{}: {} [{} fresh] {}{}", chat_prefix(&chat), chat.get_id(), chat.get_name(), @@ -533,6 +533,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ChatVisibility::Archived => "📦", ChatVisibility::Pinned => "📌", }, + if chat.is_protected() { "🛡️" } else { "" }, ); let lot = chatlist.get_summary(&context, i, Some(&chat)).await; let statestr = if chat.visibility == ChatVisibility::Archived { @@ -607,7 +608,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu format!("{} member(s)", members.len()) }; println!( - "{}#{}: {} [{}]{}{}", + "{}#{}: {} [{}]{}{} {}", chat_prefix(sel_chat), sel_chat.get_id(), sel_chat.get_name(), @@ -624,6 +625,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu }, _ => "".to_string(), }, + if sel_chat.is_protected() { + "🛡️" + } else { + "" + }, ); log_msglist(&context, &msglist).await?; if let Some(draft) = sel_chat.get_id().get_draft(&context).await? { diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 60860d5a3..30cce4c72 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -85,12 +85,12 @@ class Chat(object): """ return not lib.dc_chat_is_unpromoted(self._dc_chat) - def is_verified(self): + def is_protected(self): """ return True if this chat is a verified group. :returns: True if chat is verified, False otherwise. """ - return lib.dc_chat_is_verified(self._dc_chat) + return lib.dc_chat_is_protected(self._dc_chat) def get_name(self): """ return name of this chat. diff --git a/python/tests/test_account.py b/python/tests/test_account.py index 543317b1e..e65f27ced 100644 --- a/python/tests/test_account.py +++ b/python/tests/test_account.py @@ -1343,7 +1343,7 @@ class TestOnlineAccount: ac1, ac2 = acfactory.get_two_online_accounts() lp.sec("ac1: create verified-group QR, ac2 scans and joins") chat1 = ac1.create_group_chat("hello", verified=True) - assert chat1.is_verified() + assert chat1.is_protected() qr = chat1.get_join_qr() lp.sec("ac2: start QR-code based join-group protocol") chat2 = ac2.qr_join_chat(qr) @@ -1362,7 +1362,7 @@ class TestOnlineAccount: lp.sec("ac2: read message and check it's verified chat") msg = ac2._evtracker.wait_next_incoming_message() assert msg.text == "hello" - assert msg.chat.is_verified() + assert msg.chat.is_protected() assert msg.is_encrypted() lp.sec("ac2: send message and let ac1 read it") diff --git a/src/chat.rs b/src/chat.rs index 731ed9220..e4b199b8e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -566,6 +566,7 @@ pub struct Chat { pub param: Params, is_sending_locations: bool, pub mute_duration: MuteDuration, + protected: ProtectionStatus, } impl Chat { @@ -575,7 +576,7 @@ impl Chat { .sql .query_row( "SELECT c.type, c.name, c.grpid, c.param, c.archived, - c.blocked, c.locations_send_until, c.muted_until + c.blocked, c.locations_send_until, c.muted_until, c.protected FROM chats c WHERE c.id=?;", paramsv![chat_id], @@ -590,6 +591,7 @@ impl Chat { blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(), is_sending_locations: row.get(6)?, mute_duration: row.get(7)?, + protected: row.get(8)?, }; Ok(c) }, @@ -755,9 +757,9 @@ impl Chat { !self.is_unpromoted() } - /// Returns true if chat is a verified group chat. - pub fn is_verified(&self) -> bool { - self.typ == Chattype::VerifiedGroup + /// Returns true if chat protection is enabled. + pub fn is_protected(&self) -> bool { + self.protected == ProtectionStatus::Protected } /// Returns true if location streaming is enabled in the chat. @@ -2062,7 +2064,7 @@ pub(crate) async fn add_contact_to_chat_ex( { error!( context, - "Only bidirectional verified contacts can be added to verified groups." + "Only bidirectional verified contacts can be added to protected chats." ); return Ok(false); } @@ -2626,7 +2628,7 @@ pub(crate) async fn get_chat_cnt(context: &Context) -> usize { } } -/// Returns a tuple of `(chatid, is_verified, blocked)`. +/// Returns a tuple of `(chatid, is_protected, blocked)`. pub(crate) async fn get_chat_id_by_grpid( context: &Context, grpid: impl AsRef, @@ -2634,14 +2636,16 @@ pub(crate) async fn get_chat_id_by_grpid( context .sql .query_row( - "SELECT id, blocked, type FROM chats WHERE grpid=?;", + "SELECT id, blocked, protected FROM chats WHERE grpid=?;", paramsv![grpid.as_ref()], |row| { let chat_id = row.get::<_, ChatId>(0)?; let b = row.get::<_, Option>(1)?.unwrap_or_default(); - let v = row.get::<_, Option>(2)?.unwrap_or_default(); - Ok((chat_id, v == Chattype::VerifiedGroup, b)) + let p = row + .get::<_, Option>(2)? + .unwrap_or_default(); + Ok((chat_id, p == ProtectionStatus::Protected, b)) }, ) .await diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 29a4caf4d..9abc9de6c 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1731,7 +1731,7 @@ async fn check_verified_properties( } if !is_verified { bail!( - "{} is not a member of this verified group", + "{} is not a member of this protected chat", to_addr.to_string() ); } diff --git a/src/securejoin.rs b/src/securejoin.rs index a7299f827..02abc14c5 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -348,7 +348,7 @@ async fn securejoin(context: &Context, qr: &str) -> Result { let bob = context.bob.read().await; let grpid = bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap(); match chat::get_chat_id_by_grpid(context, grpid).await { - Ok((chatid, _is_verified, _blocked)) => break chatid, + Ok((chatid, _is_protected, _blocked)) => break chatid, Err(err) => { if start.elapsed() > Duration::from_secs(7) { return Err(JoinError::MissingChat(err)); @@ -791,19 +791,19 @@ pub(crate) async fn handle_securejoin_handshake( let vg_expect_encrypted = if join_vg { let group_id = get_qr_attr!(context, text2).to_string(); - // This is buggy, is_verified_group will always be + // This is buggy, is_protected_group will always be // false since the group is created by receive_imf by // the very handshake message we're handling now. But // only after we have returned. It does not impact // the security invariants of secure-join however. - let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, &group_id) + let (_, is_protected_group, _) = chat::get_chat_id_by_grpid(context, &group_id) .await .unwrap_or((ChatId::new(0), false, Blocked::Not)); // when joining a non-verified group // the vg-member-added message may be unencrypted // when not all group members have keys or prefer encryption. // So only expect encryption if this is a verified group - is_verified_group + is_protected_group } else { // setup contact is always encrypted true @@ -1428,6 +1428,6 @@ mod tests { let bob_chatid = joiner.await; let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await.unwrap(); - assert!(bob_chat.is_verified()); + assert!(bob_chat.is_protected()); } } From a7998c190c9b29a6ebba527d8a7a2a1ecd35e86a Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Thu, 1 Oct 2020 20:03:09 +0200 Subject: [PATCH 04/23] remove DC_CHAT_TYPE_VERIFIED_GROUP resp. Chattype::VerifiedGroup --- deltachat-ffi/deltachat.h | 4 ---- src/chat.rs | 15 ++++++--------- src/chatlist.rs | 4 +--- src/constants.rs | 1 - src/message.rs | 8 +++----- src/mimefactory.rs | 12 ++++++------ 6 files changed, 16 insertions(+), 28 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 4a14d7d3a..1372478ff 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2769,7 +2769,6 @@ char* dc_chat_get_info_json (dc_context_t* context, size_t chat #define DC_CHAT_TYPE_UNDEFINED 0 #define DC_CHAT_TYPE_SINGLE 100 #define DC_CHAT_TYPE_GROUP 120 -#define DC_CHAT_TYPE_VERIFIED_GROUP 130 /** @@ -2810,9 +2809,6 @@ uint32_t dc_chat_get_id (const dc_chat_t* chat); * - DC_CHAT_TYPE_GROUP (120) - a group chat, chats_contacts contain all group * members, incl. DC_CONTACT_ID_SELF * - * - DC_CHAT_TYPE_VERIFIED_GROUP (130) - a verified group chat. In verified groups, - * all members are verified and encryption is always active and cannot be disabled. - * * @memberof dc_chat_t * @param chat The chat object. * @return Chat type. diff --git a/src/chat.rs b/src/chat.rs index e4b199b8e..da02fac37 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -786,15 +786,12 @@ impl Chat { let mut to_id = 0; let mut location_id = 0; - if !(self.typ == Chattype::Single - || self.typ == Chattype::Group - || self.typ == Chattype::VerifiedGroup) - { + if !(self.typ == Chattype::Single || self.typ == Chattype::Group) { error!(context, "Cannot send to chat type #{}.", self.typ,); bail!("Cannot set to chat type #{}", self.typ); } - if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) + if self.typ == Chattype::Group && !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await { emit_event!( @@ -811,7 +808,7 @@ impl Chat { let new_rfc724_mid = { let grpid = match self.typ { - Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()), + Chattype::Group => Some(self.grpid.as_str()), _ => None, }; dc_create_outgoing_rfc724_mid(grpid, &from) @@ -835,7 +832,7 @@ impl Chat { ); bail!("Cannot set message, contact for {} not found.", self.id); } - } else if (self.typ == Chattype::Group || self.typ == Chattype::VerifiedGroup) + } else if self.typ == Chattype::Group && self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1 { msg.param.set_int(Param::AttachGroupImage, 1); @@ -2059,7 +2056,7 @@ pub(crate) async fn add_contact_to_chat_ex( } } else { // else continue and send status mail - if chat.typ == Chattype::VerifiedGroup + if chat.is_protected() && contact.is_verified(context).await != VerifiedStatus::BidirectVerified { error!( @@ -2102,7 +2099,7 @@ async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool { context .sql .exists( - "SELECT id FROM chats WHERE id=? AND (type=120 OR type=130);", + "SELECT id FROM chats WHERE id=? AND type=120;", paramsv![chat_id], ) .await diff --git a/src/chatlist.rs b/src/chatlist.rs index 237540d6c..7b05169a1 100644 --- a/src/chatlist.rs +++ b/src/chatlist.rs @@ -362,9 +362,7 @@ impl Chatlist { let mut lastcontact = None; let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await { - if lastmsg.from_id != DC_CONTACT_ID_SELF - && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) - { + if lastmsg.from_id != DC_CONTACT_ID_SELF && chat.typ == Chattype::Group { lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok(); } diff --git a/src/constants.rs b/src/constants.rs index b8a393520..c1b8b4b8e 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -145,7 +145,6 @@ pub enum Chattype { Undefined = 0, Single = 100, Group = 120, - VerifiedGroup = 130, } impl Default for Chattype { diff --git a/src/message.rs b/src/message.rs index 967e8efdd..28144eced 100644 --- a/src/message.rs +++ b/src/message.rs @@ -544,9 +544,7 @@ impl Message { return ret; }; - let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 - && (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup) - { + let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 && chat.typ == Chattype::Group { Contact::get_by_id(context, self.from_id).await.ok() } else { None @@ -976,7 +974,7 @@ impl Lot { ); self.text1_meaning = Meaning::Text1Self; } - } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { + } else if chat.typ == Chattype::Group { if msg.is_info() || contact.is_none() { self.text1 = None; self.text1_meaning = Meaning::None; @@ -1661,7 +1659,7 @@ pub(crate) async fn handle_ndn( if let Ok((msg_id, chat_id, chat_type)) = res { set_msg_failed(context, msg_id, error).await; - if chat_type == Chattype::Group || chat_type == Chattype::VerifiedGroup { + if chat_type == Chattype::Group { if let Some(failed_recipient) = &failed.failed_recipient { let contact_id = Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown).await; diff --git a/src/mimefactory.rs b/src/mimefactory.rs index c143ff066..22a9173e0 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -233,7 +233,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { fn is_e2ee_guaranteed(&self) -> bool { match &self.loaded { Loaded::Message { chat } => { - if chat.typ == Chattype::VerifiedGroup { + if chat.is_protected() { return true; } @@ -255,7 +255,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { fn min_verified(&self) -> PeerstateVerifiedStatus { match &self.loaded { Loaded::Message { chat } => { - if chat.typ == Chattype::VerifiedGroup { + if chat.is_protected() { PeerstateVerifiedStatus::BidirectVerified } else { PeerstateVerifiedStatus::Unverified @@ -268,7 +268,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { fn should_force_plaintext(&self) -> bool { match &self.loaded { Loaded::Message { chat } => { - if chat.typ == Chattype::VerifiedGroup { + if chat.is_protected() { false } else { self.msg @@ -345,7 +345,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> { .stock_str(StockMessage::AcSetupMsgSubject) .await .into_owned() - } else if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { + } else if chat.typ == Chattype::Group { let re = if self.in_reply_to.is_empty() { "" } else { @@ -708,11 +708,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> { let mut placeholdertext = None; let mut meta_part = None; - if chat.typ == Chattype::VerifiedGroup { + if chat.is_protected() { protected_headers.push(Header::new("Chat-Verified".to_string(), "1".to_string())); } - if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { + if chat.typ == Chattype::Group { protected_headers.push(Header::new("Chat-Group-ID".into(), chat.grpid.clone())); let encoded = encode_words(&chat.name); From 49b5962568f869e0700d71a852cd635f40aacfc6 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sat, 3 Oct 2020 20:01:47 +0200 Subject: [PATCH 05/23] add set_chat_protection() api --- deltachat-ffi/deltachat.h | 18 +++++++++ deltachat-ffi/src/lib.rs | 29 ++++++++++++++ examples/repl/cmdline.rs | 16 ++++++++ src/chat.rs | 82 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 143 insertions(+), 2 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 1372478ff..df73576e8 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -1170,6 +1170,24 @@ dc_array_t* dc_get_chat_media (dc_context_t* context, uint32_t ch uint32_t dc_get_next_media (dc_context_t* context, uint32_t msg_id, int dir, int msg_type, int msg_type2, int msg_type3); +/** + * Enable or disable protection against active attacks. + * To enable protection, it is needed that all members are verified; + * if this condition is met, end-to-end-encryption is always enabled + * and only the verified keys are used. + * + * Sends out #DC_EVENT_CHAT_MODIFIED on changes + * and #DC_EVENT_MSGS_CHANGED if a status message was sent. + * + * @memberof dc_context_t + * @param context The context object as returned from dc_context_new(). + * @param chat_id The ID of the chat to change the protection for. + * @param protect 1=protect chat, 0=unprotect chat + * @return 1=success, 0=error, eg. some members may be unverified + */ +int dc_set_chat_protection (dc_context_t* context, uint32_t chat_id, int protect); + + /** * Set chat visibility to pinned, archived or normal. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 394b1bc81..09775564c 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1057,6 +1057,35 @@ pub unsafe extern "C" fn dc_get_next_media( }) } +#[no_mangle] +pub unsafe extern "C" fn dc_set_chat_protection( + context: *mut dc_context_t, + chat_id: u32, + protect: libc::c_int, +) -> libc::c_int { + if context.is_null() { + eprintln!("ignoring careless call to dc_set_chat_protection()"); + return 0; + } + let ctx = &*context; + let protect = if let Some(s) = contact::ProtectionStatus::from_i32(protect) { + s + } else { + eprintln!("bad protect-value for dc_set_chat_protection()"); + return 0; + }; + + block_on(async move { + match ChatId::new(chat_id).set_protection(&ctx, protect).await { + Ok(()) => 1, + Err(err) => { + error!(ctx, "Cannot protect chat. Are all members verified?"); + 0 + } + } + }) +} + #[no_mangle] pub unsafe extern "C" fn dc_set_chat_visibility( context: *mut dc_context_t, diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 9682a7c55..0fd2dad4b 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -379,6 +379,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu unarchive \n\ pin \n\ unpin \n\ + protect \n\ + unprotect \n\ delchat \n\ ===========================Message commands==\n\ listmsgs \n\ @@ -918,6 +920,20 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu ) .await?; } + "protect" | "unprotect" => { + ensure!(!arg1.is_empty(), "Argument missing."); + let chat_id = ChatId::new(arg1.parse()?); + chat_id + .set_protection( + &context, + match arg0 { + "protect" => ProtectionStatus::Protected, + "unprotect" => ProtectionStatus::Unprotected, + _ => panic!("Unexpected command (This should never happen)"), + }, + ) + .await?; + } "delchat" => { ensure!(!arg1.is_empty(), "Argument missing."); let chat_id = ChatId::new(arg1.parse()?); diff --git a/src/chat.rs b/src/chat.rs index da02fac37..df1381fa5 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -174,6 +174,79 @@ impl ChatId { self.set_blocked(context, Blocked::Not).await; } + /// Set protection without sending a message. + /// Used when a message arrives indicating that someone else has + /// changed the protection value for a chat. + pub(crate) async fn inner_set_protection( + self, + context: &Context, + protect: ProtectionStatus, + send_to_others: bool, + ) -> Result<(), Error> { + ensure!(!self.is_special(), "set protection: invalid chat-id."); + + let chat = Chat::load_from_db(context, self).await?; + + if protect == chat.protected { + info!(context, "Protection status unchanged for {}.", self); + return Ok(()); + } + + match protect { + ProtectionStatus::Protected => match chat.typ { + Chattype::Single | Chattype::Group => { + let contact_ids = get_chat_contacts(context, self).await; + for contact_id in contact_ids.into_iter() { + let contact = Contact::get_by_id(context, contact_id).await?; + if contact.is_verified(context).await != VerifiedStatus::BidirectVerified { + bail!( + "{} is not verified; cannot enable protection.", + contact.get_display_name() + ); + } + } + } + Chattype::Undefined => bail!("set protection: undefined group type"), + }, + ProtectionStatus::Unprotected => {} + }; + + context + .sql + .execute( + "UPDATE chats SET protected=? WHERE id=?;", + paramsv![protect, self], + ) + .await?; + + context.emit_event(EventType::ChatModified(self)); + + if send_to_others {} + + Ok(()) + } + + pub async fn set_protection( + self, + context: &Context, + protect: ProtectionStatus, + ) -> Result<(), Error> { + ensure!(!self.is_special(), "set protection: invalid chat-id."); + + let chat = Chat::load_from_db(context, self).await?; + + match self + .inner_set_protection(context, protect, chat.is_promoted()) + .await + { + Ok(()) => Ok(()), + Err(err) => { + error!(context, "{}", err); // make error user-visible + Err(err) + } + } + } + /// Archives or unarchives a chat. pub async fn set_visibility( self, @@ -1904,13 +1977,12 @@ pub async fn create_group_chat( let grpid = dc_create_id(); context.sql.execute( - "INSERT INTO chats (type, name, grpid, param, created_timestamp, protected) VALUES(?, ?, ?, \'U=1\', ?, ?);", + "INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);", paramsv![ Chattype::Group, chat_name, grpid, time(), - protect, ], ).await?; @@ -1931,6 +2003,12 @@ pub async fn create_group_chat( chat_id: ChatId::new(0), }); + if protect == ProtectionStatus::Protected { + chat_id + .inner_set_protection(context, protect, false) + .await?; + } + Ok(chat_id) } From 5e07a36cd2617828c3ededbb36ba0fa46091e136 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 4 Oct 2020 13:47:29 +0200 Subject: [PATCH 06/23] add/send info-message on protection changes --- deltachat-ffi/deltachat.h | 7 +++++-- src/chat.rs | 28 +++++++++++++++++++++++++++- src/mimefactory.rs | 12 ++++++++++++ src/mimeparser.rs | 4 ++++ src/stock.rs | 6 ++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index df73576e8..2c7a26699 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3460,7 +3460,8 @@ int dc_msg_is_forwarded (const dc_msg_t* msg); /** * Check if the message is an informational message, created by the * device or by another users. Such messages are not "typed" by the user but - * created due to other actions, eg. dc_set_chat_name(), dc_set_chat_profile_image() + * created due to other actions, + * eg. dc_set_chat_name(), dc_set_chat_profile_image(), dc_set_chat_protection() * or dc_add_contact_to_chat(). * * These messages are typically shown in the center of the chat view, @@ -4973,8 +4974,10 @@ void dc_event_unref(dc_event_t* event); #define DC_STR_BAD_TIME_MSG_BODY 85 #define DC_STR_UPDATE_REMINDER_MSG_BODY 86 #define DC_STR_ERROR_NO_NETWORK 87 +#define DC_STR_PROTECTION_ENABLED 88 +#define DC_STR_PROTECTION_DISABLED 89 -#define DC_STR_COUNT 87 +#define DC_STR_COUNT 89 /* * @} diff --git a/src/chat.rs b/src/chat.rs index df1381fa5..8a88f6e7a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -221,7 +221,33 @@ impl ChatId { context.emit_event(EventType::ChatModified(self)); - if send_to_others {} + // make sure, the receivers will get all keys + reset_gossiped_timestamp(context, self).await?; + + // add info message + let msg_text = context + .stock_system_msg( + match protect { + ProtectionStatus::Protected => StockMessage::ProtectionEnabled, + ProtectionStatus::Unprotected => StockMessage::ProtectionDisabled, + }, + "", + "", + DC_CONTACT_ID_SELF as u32, + ) + .await; + if send_to_others { + let mut msg = Message::default(); + msg.viewtype = Viewtype::Text; + msg.text = Some(msg_text); + msg.param.set_cmd(match protect { + ProtectionStatus::Protected => SystemMessage::ChatProtectionEnabled, + ProtectionStatus::Unprotected => SystemMessage::ChatProtectionDisabled, + }); + send_msg(context, self, &mut msg).await?; + } else { + add_info_msg(context, self, msg_text).await; + } Ok(()) } diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 22a9173e0..823e1e739 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -846,6 +846,18 @@ impl<'a, 'b> MimeFactory<'a, 'b> { }; } } + SystemMessage::ChatProtectionEnabled => { + protected_headers.push(Header::new( + "Chat-Content".to_string(), + "protection-enabled".to_string(), + )); + } + SystemMessage::ChatProtectionDisabled => { + protected_headers.push(Header::new( + "Chat-Content".to_string(), + "protection-disabled".to_string(), + )); + } _ => {} } diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 933fc11b0..a4f5ed73e 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -86,6 +86,10 @@ pub enum SystemMessage { /// Chat ephemeral message timer is changed. EphemeralTimerChanged = 10, + + // Chat protection state changed + ChatProtectionEnabled = 11, + ChatProtectionDisabled = 12, } impl Default for SystemMessage { diff --git a/src/stock.rs b/src/stock.rs index 5fee21f42..9163c0295 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -233,6 +233,12 @@ pub enum StockMessage { fallback = "Could not find your mail server.\n\nPlease check your internet connection." ))] ErrorNoNetwork = 87, + + #[strum(props(fallback = "Chat protection enabled."))] + ProtectionEnabled = 88, + + #[strum(props(fallback = "Chat protection disabled."))] + ProtectionDisabled = 89, } /* From d240bbcd072df74abc4719e02db7b1ba7b1e8a87 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 4 Oct 2020 16:40:27 +0200 Subject: [PATCH 07/23] if a message is replaced by an error, this also removes special is_system_message states --- src/mimeparser.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mimeparser.rs b/src/mimeparser.rs index a4f5ed73e..9d6b63922 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -852,6 +852,7 @@ impl MimeMessage { } pub fn repl_msg_by_error(&mut self, error_msg: impl AsRef) { + self.is_system_message = SystemMessage::Unknown; if let Some(part) = self.parts.first_mut() { part.typ = Viewtype::Text; part.msg = format!("[{}]", error_msg.as_ref()); From 12cf89735c778b3c2832be32d8e75da8d7a31205 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 4 Oct 2020 17:28:21 +0200 Subject: [PATCH 08/23] handling incoming protection-changes messages, always add info-msg --- src/chat.rs | 33 ++++++++++++++++++--------------- src/dc_receive_imf.rs | 23 +++++++++++++++++++++++ src/mimeparser.rs | 4 ++++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 8a88f6e7a..57696bb76 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -181,7 +181,6 @@ impl ChatId { self, context: &Context, protect: ProtectionStatus, - send_to_others: bool, ) -> Result<(), Error> { ensure!(!self.is_special(), "set protection: invalid chat-id."); @@ -224,7 +223,16 @@ impl ChatId { // make sure, the receivers will get all keys reset_gossiped_timestamp(context, self).await?; - // add info message + Ok(()) + } + + // adds or sends out a protection-info-message + pub(crate) async fn add_protection_msg( + self, + context: &Context, + protect: ProtectionStatus, + promote: bool, + ) -> Result<(), Error> { let msg_text = context .stock_system_msg( match protect { @@ -236,7 +244,8 @@ impl ChatId { DC_CONTACT_ID_SELF as u32, ) .await; - if send_to_others { + + if promote { let mut msg = Message::default(); msg.viewtype = Viewtype::Text; msg.text = Some(msg_text); @@ -261,16 +270,12 @@ impl ChatId { let chat = Chat::load_from_db(context, self).await?; - match self - .inner_set_protection(context, protect, chat.is_promoted()) - .await - { - Ok(()) => Ok(()), - Err(err) => { - error!(context, "{}", err); // make error user-visible - Err(err) - } + if let Err(e) = self.inner_set_protection(context, protect).await { + error!(context, "{}", e); // make error user-visible + return Err(e); } + + self.add_protection_msg(context, protect, chat.is_promoted()).await } /// Archives or unarchives a chat. @@ -2030,9 +2035,7 @@ pub async fn create_group_chat( }); if protect == ProtectionStatus::Protected { - chat_id - .inner_set_protection(context, protect, false) - .await?; + chat_id.set_protection(context, protect).await?; } Ok(chat_id) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 9abc9de6c..6e1ff2071 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -714,6 +714,17 @@ async fn add_parts( ephemeral_timer = EphemeralTimer::Disabled; } + // change chat protection + if mime_parser.is_system_message == SystemMessage::ChatProtectionEnabled { + chat_id + .inner_set_protection(context, ProtectionStatus::Protected) + .await?; + } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { + chat_id + .inner_set_protection(context, ProtectionStatus::Unprotected) + .await?; + } + // correct message_timestamp, it should not be used before, // however, we cannot do this earlier as we need from_id to be set let in_fresh = state == MessageState::InFresh; @@ -1227,6 +1238,10 @@ async fn create_or_lookup_group( .await; chat_id_blocked = create_blocked; recreate_member_list = true; + + if create_protected == ProtectionStatus::Protected { + chat_id.add_protection_msg(context, ProtectionStatus::Protected, false).await?; + } } // again, check chat_id @@ -1278,7 +1293,15 @@ async fn create_or_lookup_group( } } } + } else if mime_parser.is_system_message == SystemMessage::ChatProtectionEnabled { + recreate_member_list = true; + if let Err(e) = check_verified_properties(context, mime_parser, from_id, to_ids).await { + warn!(context, "checking verified properties failed: {}", e); + let s = format!("{}. See 'Info' for more details", e); + mime_parser.repl_msg_by_error(s); + } } + if let Some(avatar_action) = &mime_parser.group_avatar { info!(context, "group-avatar change for {}", chat_id); if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await { diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 9d6b63922..42911a5bd 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -257,6 +257,10 @@ impl MimeMessage { self.is_system_message = SystemMessage::LocationStreamingEnabled; } else if value == "ephemeral-timer-changed" { self.is_system_message = SystemMessage::EphemeralTimerChanged; + } else if value == "protection-enabled" { + self.is_system_message = SystemMessage::ChatProtectionEnabled; + } else if value == "protection-disabled" { + self.is_system_message = SystemMessage::ChatProtectionDisabled; } } Ok(()) From 47f4f2bd08f0da4e27b6e6a9a45221c4af18b869 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 4 Oct 2020 18:29:36 +0200 Subject: [PATCH 09/23] use language of receiver for protection-messages, show correct sender --- src/chat.rs | 29 +++++++++++++++++++++-------- src/dc_receive_imf.rs | 18 +++++++++++++++++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 57696bb76..3c6a9647a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -226,14 +226,14 @@ impl ChatId { Ok(()) } - // adds or sends out a protection-info-message - pub(crate) async fn add_protection_msg( + /// returns a stock message saying that protection status gas changed. + pub(crate) async fn get_protection_msg( self, context: &Context, protect: ProtectionStatus, - promote: bool, - ) -> Result<(), Error> { - let msg_text = context + from_id: u32, + ) -> String { + context .stock_system_msg( match protect { ProtectionStatus::Protected => StockMessage::ProtectionEnabled, @@ -241,9 +241,20 @@ impl ChatId { }, "", "", - DC_CONTACT_ID_SELF as u32, + from_id, ) - .await; + .await + } + + /// adds or sends out a protection-info-message + pub(crate) async fn add_protection_msg( + self, + context: &Context, + protect: ProtectionStatus, + promote: bool, + from_id: u32, + ) -> Result<(), Error> { + let msg_text = self.get_protection_msg(context, protect, from_id).await; if promote { let mut msg = Message::default(); @@ -261,6 +272,7 @@ impl ChatId { Ok(()) } + /// Set protection and sends or adds a message. pub async fn set_protection( self, context: &Context, @@ -275,7 +287,8 @@ impl ChatId { return Err(e); } - self.add_protection_msg(context, protect, chat.is_promoted()).await + self.add_protection_msg(context, protect, chat.is_promoted(), DC_CONTACT_ID_SELF) + .await } /// Archives or unarchives a chat. diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 6e1ff2071..ce3228e15 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -712,6 +712,20 @@ async fn add_parts( // hour, only the message about the change to 1 // week is left. ephemeral_timer = EphemeralTimer::Disabled; + } else if mime_parser.is_system_message == SystemMessage::ChatProtectionEnabled { + set_better_msg( + mime_parser, + chat_id + .get_protection_msg(context, ProtectionStatus::Protected, from_id) + .await, + ); + } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { + set_better_msg( + mime_parser, + chat_id + .get_protection_msg(context, ProtectionStatus::Unprotected, from_id) + .await, + ); } // change chat protection @@ -1240,7 +1254,9 @@ async fn create_or_lookup_group( recreate_member_list = true; if create_protected == ProtectionStatus::Protected { - chat_id.add_protection_msg(context, ProtectionStatus::Protected, false).await?; + chat_id + .add_protection_msg(context, ProtectionStatus::Protected, false, from_id) + .await?; } } From 66cb3d43586739a3e1cb63f02a5a519eea709292 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Sun, 4 Oct 2020 18:54:21 +0200 Subject: [PATCH 10/23] fix ffi and bindings, error is already logged in core --- deltachat-ffi/src/lib.rs | 9 +++------ python/src/deltachat/chat.py | 9 +++------ src/chat.rs | 2 +- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 09775564c..4a3dbdc9d 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1068,7 +1068,7 @@ pub unsafe extern "C" fn dc_set_chat_protection( return 0; } let ctx = &*context; - let protect = if let Some(s) = contact::ProtectionStatus::from_i32(protect) { + let protect = if let Some(s) = ProtectionStatus::from_i32(protect) { s } else { eprintln!("bad protect-value for dc_set_chat_protection()"); @@ -1078,10 +1078,7 @@ pub unsafe extern "C" fn dc_set_chat_protection( block_on(async move { match ChatId::new(chat_id).set_protection(&ctx, protect).await { Ok(()) => 1, - Err(err) => { - error!(ctx, "Cannot protect chat. Are all members verified?"); - 0 - } + Err(_) => 0, } }) } @@ -1207,7 +1204,7 @@ pub unsafe extern "C" fn dc_create_group_chat( return 0; } let ctx = &*context; - let protect = if let Some(s) = contact::ProtectionStatus::from_i32(protect) { + let protect = if let Some(s) = ProtectionStatus::from_i32(protect) { s } else { return 0; diff --git a/python/src/deltachat/chat.py b/python/src/deltachat/chat.py index 30cce4c72..bd3890b70 100644 --- a/python/src/deltachat/chat.py +++ b/python/src/deltachat/chat.py @@ -57,10 +57,7 @@ class Chat(object): :returns: True if chat is a group-chat, false if it's a contact 1:1 chat. """ - return lib.dc_chat_get_type(self._dc_chat) in ( - const.DC_CHAT_TYPE_GROUP, - const.DC_CHAT_TYPE_VERIFIED_GROUP - ) + return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP def is_deaddrop(self): """ return true if this chat is a deaddrop chat. @@ -86,9 +83,9 @@ class Chat(object): return not lib.dc_chat_is_unpromoted(self._dc_chat) def is_protected(self): - """ return True if this chat is a verified group. + """ return True if this chat is a protected chat. - :returns: True if chat is verified, False otherwise. + :returns: True if chat is protected, False otherwise. """ return lib.dc_chat_is_protected(self._dc_chat) diff --git a/src/chat.rs b/src/chat.rs index 3c6a9647a..ef863b231 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -1144,7 +1144,7 @@ pub struct ChatInfo { /// /// On the C API this number is one of the /// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`, - /// `DC_CHAT_TYPE_GROUP` or `DC_CHAT_TYPE_VERIFIED_GROUP` + /// or `DC_CHAT_TYPE_GROUP` /// constants. #[serde(rename = "type")] pub type_: u32, From c1768bb31173a11e6bec8342e3c586d92de9381e Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 5 Oct 2020 00:59:00 +0200 Subject: [PATCH 11/23] add dc_msg_get_info_type() to allow drawing a shield in the uis --- deltachat-ffi/deltachat.h | 26 ++++++++++++++++++++++++++ deltachat-ffi/src/lib.rs | 10 ++++++++++ src/message.rs | 4 ++++ 3 files changed, 40 insertions(+) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 2c7a26699..c1b711670 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -3477,6 +3477,32 @@ int dc_msg_is_forwarded (const dc_msg_t* msg); int dc_msg_is_info (const dc_msg_t* msg); +/** + * Get the type of an informational message. + * If dc_msg_is_info() returns 1, this function returns the type of the informational message. + * UIs can display eg. an icon based upon the type. + * + * Currently, the following types are defined: + * - DC_INFO_PROTECTION_ENABLED (11) - Info-message for "Chat is now protected" + * - DC_INFO_PROTECTION_DISABLED (12) - Info-message for "Chat is no longer protected" + * + * Even when you display an icon, + * you should still display the text of the informational message using dc_msg_get_text() + * + * @memberof dc_msg_t + * @param msg The message object. + * @return One of the DC_INFO* constants. + * 0 or other values indicate unspecified types + * or that the message is not an info-message. + */ +int dc_msg_get_info_type (const dc_msg_t* msg); + + +// DC_INFO* uses the same values as SystemMessage in rust-land +#define DC_INFO_PROTECTION_ENABLED 11 +#define DC_INFO_PROTECTION_DISABLED 12 + + /** * Check if a message is still in creation. A message is in creation between * the calls to dc_prepare_msg() and dc_send_msg(). diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 4a3dbdc9d..7b73c0d0b 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2843,6 +2843,16 @@ pub unsafe extern "C" fn dc_msg_is_info(msg: *mut dc_msg_t) -> libc::c_int { ffi_msg.message.is_info().into() } +#[no_mangle] +pub unsafe extern "C" fn dc_msg_get_info_type(msg: *mut dc_msg_t) -> libc::c_int { + if msg.is_null() { + eprintln!("ignoring careless call to dc_msg_get_info_type()"); + return 0; + } + let ffi_msg = &*msg; + ffi_msg.message.get_info_type() as libc::c_int +} + #[no_mangle] pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int { if msg.is_null() { diff --git a/src/message.rs b/src/message.rs index 28144eced..577aa193f 100644 --- a/src/message.rs +++ b/src/message.rs @@ -589,6 +589,10 @@ impl Message { || cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage } + pub fn get_info_type(&self) -> SystemMessage { + self.param.get_cmd() + } + pub fn is_system_message(&self) -> bool { let cmd = self.param.get_cmd(); cmd != SystemMessage::Unknown From e447bdc0c3cc6000d24e2a672e70cc014997a27c Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 5 Oct 2020 01:32:45 +0200 Subject: [PATCH 12/23] simplify code-path --- src/dc_receive_imf.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index ce3228e15..ceab5133a 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -712,20 +712,6 @@ async fn add_parts( // hour, only the message about the change to 1 // week is left. ephemeral_timer = EphemeralTimer::Disabled; - } else if mime_parser.is_system_message == SystemMessage::ChatProtectionEnabled { - set_better_msg( - mime_parser, - chat_id - .get_protection_msg(context, ProtectionStatus::Protected, from_id) - .await, - ); - } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { - set_better_msg( - mime_parser, - chat_id - .get_protection_msg(context, ProtectionStatus::Unprotected, from_id) - .await, - ); } // change chat protection @@ -733,10 +719,22 @@ async fn add_parts( chat_id .inner_set_protection(context, ProtectionStatus::Protected) .await?; + set_better_msg( + mime_parser, + chat_id + .get_protection_msg(context, ProtectionStatus::Protected, from_id) + .await, + ); } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { chat_id .inner_set_protection(context, ProtectionStatus::Unprotected) .await?; + set_better_msg( + mime_parser, + chat_id + .get_protection_msg(context, ProtectionStatus::Unprotected, from_id) + .await, + ); } // correct message_timestamp, it should not be used before, From f144426bf5f5ddbe4755b4e437373dce4dc6ecb2 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Mon, 5 Oct 2020 20:54:15 +0200 Subject: [PATCH 13/23] use 'unreachable' instead of 'panic' --- examples/repl/cmdline.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/repl/cmdline.rs b/examples/repl/cmdline.rs index 0fd2dad4b..e949a34e2 100644 --- a/examples/repl/cmdline.rs +++ b/examples/repl/cmdline.rs @@ -915,7 +915,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu "archive" => ChatVisibility::Archived, "unarchive" | "unpin" => ChatVisibility::Normal, "pin" => ChatVisibility::Pinned, - _ => panic!("Unexpected command (This should never happen)"), + _ => unreachable!("arg0={:?}", arg0), }, ) .await?; @@ -929,7 +929,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu match arg0 { "protect" => ProtectionStatus::Protected, "unprotect" => ProtectionStatus::Unprotected, - _ => panic!("Unexpected command (This should never happen)"), + _ => unreachable!("arg0={:?}", arg0), }, ) .await?; From 45dae1ff0cf00c0a2a0693ce8de41a1205a07b8f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 6 Oct 2020 15:19:58 +0200 Subject: [PATCH 14/23] protect against attackers dropping the protect-this-chat message by not showing unprotected messages directly; this is done by checking the Chat-Verified flag on each incoming message. moreover, make sure, the flag is signed+encrypted (it must be read from the protected headers). --- src/dc_receive_imf.rs | 5 +++++ src/mimeparser.rs | 1 + 2 files changed, 6 insertions(+) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index ceab5133a..3109d9bab 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -1679,6 +1679,11 @@ async fn check_verified_properties( ensure!(mimeparser.was_encrypted(), "This message is not encrypted."); + ensure!( + mimeparser.get(HeaderDef::ChatVerified).is_some(), + "Sender did not mark the message as protected." + ); + // ensure, the contact is verified // and the message is signed with a verified key of the sender. // this check is skipped for SELF as there is no proper SELF-peerstate diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 42911a5bd..eddbf6f07 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -127,6 +127,7 @@ impl MimeMessage { // remove headers that are allowed _only_ in the encrypted part headers.remove("secure-join-fingerprint"); + headers.remove("chat-verified"); // Memory location for a possible decrypted message. let mail_raw; From 3a993a4b77e8ad8222112f1c25c0243c9af0da42 Mon Sep 17 00:00:00 2001 From: bjoern Date: Fri, 9 Oct 2020 00:09:27 +0200 Subject: [PATCH 15/23] Update src/chat.rs Co-authored-by: Floris Bruynooghe --- src/chat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chat.rs b/src/chat.rs index ef863b231..304c455cf 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -226,7 +226,7 @@ impl ChatId { Ok(()) } - /// returns a stock message saying that protection status gas changed. + /// returns a stock message saying that protection status has changed. pub(crate) async fn get_protection_msg( self, context: &Context, From da727740ab45f780082243745d00512a9b1e1e0e Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 9 Oct 2020 00:22:29 +0200 Subject: [PATCH 16/23] use warn! for ffi-usage-errors --- deltachat-ffi/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index 7b73c0d0b..d9c617af1 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -1071,7 +1071,7 @@ pub unsafe extern "C" fn dc_set_chat_protection( let protect = if let Some(s) = ProtectionStatus::from_i32(protect) { s } else { - eprintln!("bad protect-value for dc_set_chat_protection()"); + warn!(ctx, "bad protect-value for dc_set_chat_protection()"); return 0; }; @@ -1207,6 +1207,7 @@ pub unsafe extern "C" fn dc_create_group_chat( let protect = if let Some(s) = ProtectionStatus::from_i32(protect) { s } else { + warn!(ctx, "bad protect-value for dc_create_group_chat()"); return 0; }; From 4eb8d3fef62761a359e6acf1c34716bfd9261208 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 9 Oct 2020 00:28:44 +0200 Subject: [PATCH 17/23] tweak documentation towards https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text - thanks @flub --- src/chat.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 304c455cf..0b031d79e 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -174,7 +174,8 @@ impl ChatId { self.set_blocked(context, Blocked::Not).await; } - /// Set protection without sending a message. + /// Sets protection without sending a message. + /// /// Used when a message arrives indicating that someone else has /// changed the protection value for a chat. pub(crate) async fn inner_set_protection( @@ -226,7 +227,7 @@ impl ChatId { Ok(()) } - /// returns a stock message saying that protection status has changed. + /// Returns a stock message saying that protection status has changed. pub(crate) async fn get_protection_msg( self, context: &Context, @@ -246,7 +247,7 @@ impl ChatId { .await } - /// adds or sends out a protection-info-message + /// Adds or sends out a protection-info-message. pub(crate) async fn add_protection_msg( self, context: &Context, @@ -272,7 +273,7 @@ impl ChatId { Ok(()) } - /// Set protection and sends or adds a message. + /// Sets protection and sends or adds a message. pub async fn set_protection( self, context: &Context, From 03d86360d6425244399e3471f1d4950a762184bb Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 9 Oct 2020 00:36:01 +0200 Subject: [PATCH 18/23] details docstring, thanks @flub --- src/chat.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/chat.rs b/src/chat.rs index 0b031d79e..e2eff0801 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -247,7 +247,13 @@ impl ChatId { .await } - /// Adds or sends out a protection-info-message. + /// Send protected status message to the chat. + /// + /// This sends the message with the protected status change to the chat, + /// notifying the user on this device as well as the other users in the chat. + /// If `promoted` is false this means the chat only exists on this device so far + /// and does not need to be sent out. + /// In this case an local info message is added to the chat. pub(crate) async fn add_protection_msg( self, context: &Context, From 1e2e042244cdff3a4af78f0743abc9d696d98e1f Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Fri, 9 Oct 2020 00:43:36 +0200 Subject: [PATCH 19/23] clarify that signature of add_protection_msg() takes chat-promoted parameter --- src/chat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index e2eff0801..9c8e8e9e5 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -258,12 +258,12 @@ impl ChatId { self, context: &Context, protect: ProtectionStatus, - promote: bool, + promoted: bool, from_id: u32, ) -> Result<(), Error> { let msg_text = self.get_protection_msg(context, protect, from_id).await; - if promote { + if promoted { let mut msg = Message::default(); msg.viewtype = Viewtype::Text; msg.text = Some(msg_text); From 9fc6bbf41fe41e85e9368f3cb2e4e35f11a78e3b Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 13 Oct 2020 12:29:34 +0200 Subject: [PATCH 20/23] tweak error texts --- src/chat.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 9c8e8e9e5..2b90d9462 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -183,7 +183,7 @@ impl ChatId { context: &Context, protect: ProtectionStatus, ) -> Result<(), Error> { - ensure!(!self.is_special(), "set protection: invalid chat-id."); + ensure!(!self.is_special(), "Invalid chat-id."); let chat = Chat::load_from_db(context, self).await?; @@ -199,14 +199,11 @@ impl ChatId { for contact_id in contact_ids.into_iter() { let contact = Contact::get_by_id(context, contact_id).await?; if contact.is_verified(context).await != VerifiedStatus::BidirectVerified { - bail!( - "{} is not verified; cannot enable protection.", - contact.get_display_name() - ); + bail!("{} is not verified.", contact.get_display_name()); } } } - Chattype::Undefined => bail!("set protection: undefined group type"), + Chattype::Undefined => bail!("Undefined group type"), }, ProtectionStatus::Unprotected => {} }; @@ -290,7 +287,7 @@ impl ChatId { let chat = Chat::load_from_db(context, self).await?; if let Err(e) = self.inner_set_protection(context, protect).await { - error!(context, "{}", e); // make error user-visible + error!(context, "Cannot set protection: {}", e); // make error user-visible return Err(e); } From 8425e23d82a44f14dacf6a3a5e81abf0b7b054a9 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 13 Oct 2020 13:06:37 +0200 Subject: [PATCH 21/23] move get_protection_msg() to stock.rs as stock_protection_msg() --- src/chat.rs | 22 +--------------------- src/dc_receive_imf.rs | 8 ++++---- src/stock.rs | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/chat.rs b/src/chat.rs index 2b90d9462..17353eb7a 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -224,26 +224,6 @@ impl ChatId { Ok(()) } - /// Returns a stock message saying that protection status has changed. - pub(crate) async fn get_protection_msg( - self, - context: &Context, - protect: ProtectionStatus, - from_id: u32, - ) -> String { - context - .stock_system_msg( - match protect { - ProtectionStatus::Protected => StockMessage::ProtectionEnabled, - ProtectionStatus::Unprotected => StockMessage::ProtectionDisabled, - }, - "", - "", - from_id, - ) - .await - } - /// Send protected status message to the chat. /// /// This sends the message with the protected status change to the chat, @@ -258,7 +238,7 @@ impl ChatId { promoted: bool, from_id: u32, ) -> Result<(), Error> { - let msg_text = self.get_protection_msg(context, protect, from_id).await; + let msg_text = context.stock_protection_msg(protect, from_id).await; if promoted { let mut msg = Message::default(); diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 3109d9bab..9a6277e5e 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -721,8 +721,8 @@ async fn add_parts( .await?; set_better_msg( mime_parser, - chat_id - .get_protection_msg(context, ProtectionStatus::Protected, from_id) + context + .stock_protection_msg(ProtectionStatus::Protected, from_id) .await, ); } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { @@ -731,8 +731,8 @@ async fn add_parts( .await?; set_better_msg( mime_parser, - chat_id - .get_protection_msg(context, ProtectionStatus::Unprotected, from_id) + context + .stock_protection_msg(ProtectionStatus::Unprotected, from_id) .await, ); } diff --git a/src/stock.rs b/src/stock.rs index 9163c0295..7ebcb40c3 100644 --- a/src/stock.rs +++ b/src/stock.rs @@ -7,6 +7,7 @@ use strum_macros::EnumProperty; use crate::blob::BlobObject; use crate::chat; +use crate::chat::ProtectionStatus; use crate::constants::{Viewtype, DC_CONTACT_ID_SELF}; use crate::contact::*; use crate::context::Context; @@ -404,6 +405,20 @@ impl Context { } } + /// Returns a stock message saying that protection status has changed. + pub async fn stock_protection_msg(&self, protect: ProtectionStatus, from_id: u32) -> String { + self.stock_system_msg( + match protect { + ProtectionStatus::Protected => StockMessage::ProtectionEnabled, + ProtectionStatus::Unprotected => StockMessage::ProtectionDisabled, + }, + "", + "", + from_id, + ) + .await + } + pub async fn update_device_chats(&self) -> Result<(), Error> { // check for the LAST added device message - if it is present, we can skip message creation. // this is worthwhile as this function is typically called From 50f3af58f8392e14292c4757b87dde88bb339515 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 13 Oct 2020 13:33:32 +0200 Subject: [PATCH 22/23] remove dc_chat_is_verified() --- deltachat-ffi/deltachat.h | 1 - 1 file changed, 1 deletion(-) diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index c1b711670..2856b1213 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2966,7 +2966,6 @@ int dc_chat_can_send (const dc_chat_t* chat); * @return 1=chat protected, 0=chat is not protected */ int dc_chat_is_protected (const dc_chat_t* chat); -#define dc_chat_is_verified dc_chat_is_protected // allow using old function name for a while /** From 7f882a640635dd9abe583324d1ccfe28f8b7d765 Mon Sep 17 00:00:00 2001 From: "B. Petersen" Date: Tue, 13 Oct 2020 14:04:38 +0200 Subject: [PATCH 23/23] less duplicate code on calling inner_set_protection() --- src/dc_receive_imf.rs | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index 9a6277e5e..f10acf939 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -715,25 +715,15 @@ async fn add_parts( } // change chat protection - if mime_parser.is_system_message == SystemMessage::ChatProtectionEnabled { - chat_id - .inner_set_protection(context, ProtectionStatus::Protected) - .await?; + if let Some(new_status) = match mime_parser.is_system_message { + SystemMessage::ChatProtectionEnabled => Some(ProtectionStatus::Protected), + SystemMessage::ChatProtectionDisabled => Some(ProtectionStatus::Unprotected), + _ => None, + } { + chat_id.inner_set_protection(context, new_status).await?; set_better_msg( mime_parser, - context - .stock_protection_msg(ProtectionStatus::Protected, from_id) - .await, - ); - } else if mime_parser.is_system_message == SystemMessage::ChatProtectionDisabled { - chat_id - .inner_set_protection(context, ProtectionStatus::Unprotected) - .await?; - set_better_msg( - mime_parser, - context - .stock_protection_msg(ProtectionStatus::Unprotected, from_id) - .await, + context.stock_protection_msg(new_status, from_id).await, ); }