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) }