mirror of
https://github.com/chatmail/core.git
synced 2026-05-22 16:26:31 +03:00
Merge "protected groups" branch into master
This commit is contained in:
@@ -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);
|
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.
|
* Set chat visibility to pinned, archived or normal.
|
||||||
*
|
*
|
||||||
@@ -1299,15 +1317,15 @@ dc_chat_t* dc_get_chat (dc_context_t* context, uint32_t ch
|
|||||||
*
|
*
|
||||||
* @memberof dc_context_t
|
* @memberof dc_context_t
|
||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @param verified If set to 1 the function creates a secure verified group.
|
* @param protect If set to 1 the function creates group with protection initially enabled.
|
||||||
* Only secure-verified members are allowed in these groups
|
* Only verified members are allowed in these groups
|
||||||
* and end-to-end-encryption is always enabled.
|
* and end-to-end-encryption is always enabled.
|
||||||
* @param name The name of the group chat to create.
|
* @param name The name of the group chat to create.
|
||||||
* The name may be changed later using dc_set_chat_name().
|
* 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()
|
* 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.
|
* @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);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1329,7 +1347,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),
|
* 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.
|
* 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.
|
* Sends out #DC_EVENT_CHAT_MODIFIED and #DC_EVENT_MSGS_CHANGED if a status message was sent.
|
||||||
*
|
*
|
||||||
@@ -1973,7 +1991,7 @@ dc_lot_t* dc_check_qr (dc_context_t* context, const char*
|
|||||||
* @param context The context object.
|
* @param context The context object.
|
||||||
* @param chat_id If set to a group-chat-id,
|
* @param chat_id If set to a group-chat-id,
|
||||||
* the Verified-Group-Invite protocol is offered in the QR code;
|
* 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.
|
* If set to 0, the Setup-Contact protocol is offered in the QR code.
|
||||||
* See https://countermitm.readthedocs.io/en/latest/new.html
|
* See https://countermitm.readthedocs.io/en/latest/new.html
|
||||||
* for details about both protocols.
|
* for details about both protocols.
|
||||||
@@ -2003,7 +2021,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.
|
* When the protocol has finished, an info-message is added to that chat.
|
||||||
* - If the given QR code starts the Verified-Group-Invite protocol,
|
* - If the given QR code starts the Verified-Group-Invite protocol,
|
||||||
* the function waits until the protocol has finished.
|
* 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.
|
* and can be created only when the contacts have verified each other.
|
||||||
*
|
*
|
||||||
* See https://countermitm.readthedocs.io/en/latest/new.html
|
* See https://countermitm.readthedocs.io/en/latest/new.html
|
||||||
@@ -2015,8 +2033,8 @@ char* dc_get_securejoin_qr (dc_context_t* context, uint32_t ch
|
|||||||
* to dc_check_qr().
|
* to dc_check_qr().
|
||||||
* @return Chat-id of the joined chat, the UI may redirect to the this chat.
|
* @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.
|
* 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.
|
* 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_verified() and dc_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.
|
* 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);
|
uint32_t dc_join_securejoin (dc_context_t* context, const char* qr);
|
||||||
@@ -2769,7 +2787,6 @@ char* dc_chat_get_info_json (dc_context_t* context, size_t chat
|
|||||||
#define DC_CHAT_TYPE_UNDEFINED 0
|
#define DC_CHAT_TYPE_UNDEFINED 0
|
||||||
#define DC_CHAT_TYPE_SINGLE 100
|
#define DC_CHAT_TYPE_SINGLE 100
|
||||||
#define DC_CHAT_TYPE_GROUP 120
|
#define DC_CHAT_TYPE_GROUP 120
|
||||||
#define DC_CHAT_TYPE_VERIFIED_GROUP 130
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2810,9 +2827,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
|
* - DC_CHAT_TYPE_GROUP (120) - a group chat, chats_contacts contain all group
|
||||||
* members, incl. DC_CONTACT_ID_SELF
|
* 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
|
* @memberof dc_chat_t
|
||||||
* @param chat The chat object.
|
* @param chat The chat object.
|
||||||
* @return Chat type.
|
* @return Chat type.
|
||||||
@@ -2942,15 +2956,16 @@ int dc_chat_can_send (const dc_chat_t* chat);
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a chat is verified. Verified chats contain only verified members
|
* Check if a chat is protected.
|
||||||
* and encryption is alwasy enabled. Verified chats are created using
|
* Protected chats contain only verified members and encryption is always enabled.
|
||||||
* dc_create_group_chat() by setting the 'verified' parameter to true.
|
* 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
|
* @memberof dc_chat_t
|
||||||
* @param chat The chat object.
|
* @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);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -3444,7 +3459,8 @@ int dc_msg_is_forwarded (const dc_msg_t* msg);
|
|||||||
/**
|
/**
|
||||||
* Check if the message is an informational message, created by the
|
* 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
|
* 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().
|
* or dc_add_contact_to_chat().
|
||||||
*
|
*
|
||||||
* These messages are typically shown in the center of the chat view,
|
* These messages are typically shown in the center of the chat view,
|
||||||
@@ -3460,6 +3476,32 @@ int dc_msg_is_forwarded (const dc_msg_t* msg);
|
|||||||
int dc_msg_is_info (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
|
* Check if a message is still in creation. A message is in creation between
|
||||||
* the calls to dc_prepare_msg() and dc_send_msg().
|
* the calls to dc_prepare_msg() and dc_send_msg().
|
||||||
@@ -4957,8 +4999,10 @@ void dc_event_unref(dc_event_t* event);
|
|||||||
#define DC_STR_BAD_TIME_MSG_BODY 85
|
#define DC_STR_BAD_TIME_MSG_BODY 85
|
||||||
#define DC_STR_UPDATE_REMINDER_MSG_BODY 86
|
#define DC_STR_UPDATE_REMINDER_MSG_BODY 86
|
||||||
#define DC_STR_ERROR_NO_NETWORK 87
|
#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
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @}
|
* @}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use async_std::task::{block_on, spawn};
|
|||||||
use num_traits::{FromPrimitive, ToPrimitive};
|
use num_traits::{FromPrimitive, ToPrimitive};
|
||||||
|
|
||||||
use deltachat::accounts::Accounts;
|
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::constants::DC_MSG_ID_LAST_SPECIAL;
|
||||||
use deltachat::contact::{Contact, Origin};
|
use deltachat::contact::{Contact, Origin};
|
||||||
use deltachat::context::Context;
|
use deltachat::context::Context;
|
||||||
@@ -1057,6 +1057,32 @@ 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) = ProtectionStatus::from_i32(protect) {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
warn!(ctx, "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(_) => 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_set_chat_visibility(
|
pub unsafe extern "C" fn dc_set_chat_visibility(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
@@ -1170,7 +1196,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_create_group_chat(
|
pub unsafe extern "C" fn dc_create_group_chat(
|
||||||
context: *mut dc_context_t,
|
context: *mut dc_context_t,
|
||||||
verified: libc::c_int,
|
protect: libc::c_int,
|
||||||
name: *const libc::c_char,
|
name: *const libc::c_char,
|
||||||
) -> u32 {
|
) -> u32 {
|
||||||
if context.is_null() || name.is_null() {
|
if context.is_null() || name.is_null() {
|
||||||
@@ -1178,14 +1204,15 @@ pub unsafe extern "C" fn dc_create_group_chat(
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ctx = &*context;
|
let ctx = &*context;
|
||||||
let verified = if let Some(s) = contact::VerifiedStatus::from_i32(verified) {
|
let protect = if let Some(s) = ProtectionStatus::from_i32(protect) {
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
|
warn!(ctx, "bad protect-value for dc_create_group_chat()");
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
block_on(async move {
|
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
|
.await
|
||||||
.log_err(ctx, "Failed to create group chat")
|
.log_err(ctx, "Failed to create group chat")
|
||||||
.map(|id| id.to_u32())
|
.map(|id| id.to_u32())
|
||||||
@@ -2389,13 +2416,13 @@ pub unsafe extern "C" fn dc_chat_can_send(chat: *mut dc_chat_t) -> libc::c_int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[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() {
|
if chat.is_null() {
|
||||||
eprintln!("ignoring careless call to dc_chat_is_verified()");
|
eprintln!("ignoring careless call to dc_chat_is_protected()");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
let ffi_chat = &*chat;
|
let ffi_chat = &*chat;
|
||||||
ffi_chat.chat.is_verified() as libc::c_int
|
ffi_chat.chat.is_protected() as libc::c_int
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@@ -2817,6 +2844,16 @@ pub unsafe extern "C" fn dc_msg_is_info(msg: *mut dc_msg_t) -> libc::c_int {
|
|||||||
ffi_msg.message.is_info().into()
|
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]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
|
pub unsafe extern "C" fn dc_msg_is_increation(msg: *mut dc_msg_t) -> libc::c_int {
|
||||||
if msg.is_null() {
|
if msg.is_null() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use anyhow::{bail, ensure};
|
use anyhow::{bail, ensure};
|
||||||
use async_std::path::Path;
|
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::chatlist::*;
|
||||||
use deltachat::constants::*;
|
use deltachat::constants::*;
|
||||||
use deltachat::contact::*;
|
use deltachat::contact::*;
|
||||||
@@ -357,7 +357,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
createchat <contact-id>\n\
|
createchat <contact-id>\n\
|
||||||
createchatbymsg <msg-id>\n\
|
createchatbymsg <msg-id>\n\
|
||||||
creategroup <name>\n\
|
creategroup <name>\n\
|
||||||
createverified <name>\n\
|
createprotected <name>\n\
|
||||||
addmember <contact-id>\n\
|
addmember <contact-id>\n\
|
||||||
removemember <contact-id>\n\
|
removemember <contact-id>\n\
|
||||||
groupname <name>\n\
|
groupname <name>\n\
|
||||||
@@ -379,6 +379,8 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
unarchive <chat-id>\n\
|
unarchive <chat-id>\n\
|
||||||
pin <chat-id>\n\
|
pin <chat-id>\n\
|
||||||
unpin <chat-id>\n\
|
unpin <chat-id>\n\
|
||||||
|
protect <chat-id>\n\
|
||||||
|
unprotect <chat-id>\n\
|
||||||
delchat <chat-id>\n\
|
delchat <chat-id>\n\
|
||||||
===========================Message commands==\n\
|
===========================Message commands==\n\
|
||||||
listmsgs <query>\n\
|
listmsgs <query>\n\
|
||||||
@@ -523,7 +525,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
for i in (0..cnt).rev() {
|
for i in (0..cnt).rev() {
|
||||||
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?;
|
let chat = Chat::load_from_db(&context, chatlist.get_chat_id(i)).await?;
|
||||||
println!(
|
println!(
|
||||||
"{}#{}: {} [{} fresh] {}",
|
"{}#{}: {} [{} fresh] {}{}",
|
||||||
chat_prefix(&chat),
|
chat_prefix(&chat),
|
||||||
chat.get_id(),
|
chat.get_id(),
|
||||||
chat.get_name(),
|
chat.get_name(),
|
||||||
@@ -533,6 +535,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
ChatVisibility::Archived => "📦",
|
ChatVisibility::Archived => "📦",
|
||||||
ChatVisibility::Pinned => "📌",
|
ChatVisibility::Pinned => "📌",
|
||||||
},
|
},
|
||||||
|
if chat.is_protected() { "🛡️" } else { "" },
|
||||||
);
|
);
|
||||||
let lot = chatlist.get_summary(&context, i, Some(&chat)).await;
|
let lot = chatlist.get_summary(&context, i, Some(&chat)).await;
|
||||||
let statestr = if chat.visibility == ChatVisibility::Archived {
|
let statestr = if chat.visibility == ChatVisibility::Archived {
|
||||||
@@ -607,7 +610,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
format!("{} member(s)", members.len())
|
format!("{} member(s)", members.len())
|
||||||
};
|
};
|
||||||
println!(
|
println!(
|
||||||
"{}#{}: {} [{}]{}{}",
|
"{}#{}: {} [{}]{}{} {}",
|
||||||
chat_prefix(sel_chat),
|
chat_prefix(sel_chat),
|
||||||
sel_chat.get_id(),
|
sel_chat.get_id(),
|
||||||
sel_chat.get_name(),
|
sel_chat.get_name(),
|
||||||
@@ -624,6 +627,11 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
},
|
},
|
||||||
_ => "".to_string(),
|
_ => "".to_string(),
|
||||||
},
|
},
|
||||||
|
if sel_chat.is_protected() {
|
||||||
|
"🛡️"
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
},
|
||||||
);
|
);
|
||||||
log_msglist(&context, &msglist).await?;
|
log_msglist(&context, &msglist).await?;
|
||||||
if let Some(draft) = sel_chat.get_id().get_draft(&context).await? {
|
if let Some(draft) = sel_chat.get_id().get_draft(&context).await? {
|
||||||
@@ -654,15 +662,16 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"creategroup" => {
|
"creategroup" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
||||||
let chat_id =
|
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);
|
println!("Group#{} created successfully.", chat_id);
|
||||||
}
|
}
|
||||||
"createverified" => {
|
"createprotected" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <name> missing.");
|
ensure!(!arg1.is_empty(), "Argument <name> 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" => {
|
"addmember" => {
|
||||||
ensure!(sel_chat.is_some(), "No chat selected");
|
ensure!(sel_chat.is_some(), "No chat selected");
|
||||||
@@ -906,7 +915,21 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
"archive" => ChatVisibility::Archived,
|
"archive" => ChatVisibility::Archived,
|
||||||
"unarchive" | "unpin" => ChatVisibility::Normal,
|
"unarchive" | "unpin" => ChatVisibility::Normal,
|
||||||
"pin" => ChatVisibility::Pinned,
|
"pin" => ChatVisibility::Pinned,
|
||||||
_ => panic!("Unexpected command (This should never happen)"),
|
_ => unreachable!("arg0={:?}", arg0),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
"protect" | "unprotect" => {
|
||||||
|
ensure!(!arg1.is_empty(), "Argument <chat-id> missing.");
|
||||||
|
let chat_id = ChatId::new(arg1.parse()?);
|
||||||
|
chat_id
|
||||||
|
.set_protection(
|
||||||
|
&context,
|
||||||
|
match arg0 {
|
||||||
|
"protect" => ProtectionStatus::Protected,
|
||||||
|
"unprotect" => ProtectionStatus::Unprotected,
|
||||||
|
_ => unreachable!("arg0={:?}", arg0),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -57,10 +57,7 @@ class Chat(object):
|
|||||||
|
|
||||||
:returns: True if chat is a group-chat, false if it's a contact 1:1 chat.
|
: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 (
|
return lib.dc_chat_get_type(self._dc_chat) == const.DC_CHAT_TYPE_GROUP
|
||||||
const.DC_CHAT_TYPE_GROUP,
|
|
||||||
const.DC_CHAT_TYPE_VERIFIED_GROUP
|
|
||||||
)
|
|
||||||
|
|
||||||
def is_deaddrop(self):
|
def is_deaddrop(self):
|
||||||
""" return true if this chat is a deaddrop chat.
|
""" return true if this chat is a deaddrop chat.
|
||||||
@@ -85,12 +82,12 @@ class Chat(object):
|
|||||||
"""
|
"""
|
||||||
return not lib.dc_chat_is_unpromoted(self._dc_chat)
|
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.
|
""" 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_verified(self._dc_chat)
|
return lib.dc_chat_is_protected(self._dc_chat)
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
""" return name of this chat.
|
""" return name of this chat.
|
||||||
|
|||||||
@@ -1343,7 +1343,7 @@ class TestOnlineAccount:
|
|||||||
ac1, ac2 = acfactory.get_two_online_accounts()
|
ac1, ac2 = acfactory.get_two_online_accounts()
|
||||||
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
lp.sec("ac1: create verified-group QR, ac2 scans and joins")
|
||||||
chat1 = ac1.create_group_chat("hello", verified=True)
|
chat1 = ac1.create_group_chat("hello", verified=True)
|
||||||
assert chat1.is_verified()
|
assert chat1.is_protected()
|
||||||
qr = chat1.get_join_qr()
|
qr = chat1.get_join_qr()
|
||||||
lp.sec("ac2: start QR-code based join-group protocol")
|
lp.sec("ac2: start QR-code based join-group protocol")
|
||||||
chat2 = ac2.qr_join_chat(qr)
|
chat2 = ac2.qr_join_chat(qr)
|
||||||
@@ -1362,7 +1362,7 @@ class TestOnlineAccount:
|
|||||||
lp.sec("ac2: read message and check it's verified chat")
|
lp.sec("ac2: read message and check it's verified chat")
|
||||||
msg = ac2._evtracker.wait_next_incoming_message()
|
msg = ac2._evtracker.wait_next_incoming_message()
|
||||||
assert msg.text == "hello"
|
assert msg.text == "hello"
|
||||||
assert msg.chat.is_verified()
|
assert msg.chat.is_protected()
|
||||||
assert msg.is_encrypted()
|
assert msg.is_encrypted()
|
||||||
|
|
||||||
lp.sec("ac2: send message and let ac1 read it")
|
lp.sec("ac2: send message and let ac1 read it")
|
||||||
|
|||||||
190
src/chat.rs
190
src/chat.rs
@@ -1,5 +1,6 @@
|
|||||||
//! # Chat module
|
//! # Chat module
|
||||||
|
|
||||||
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::time::{Duration, SystemTime};
|
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.
|
/// Chat ID, including reserved IDs.
|
||||||
///
|
///
|
||||||
/// Some chat IDs are reserved to identify special chat types. This
|
/// Some chat IDs are reserved to identify special chat types. This
|
||||||
@@ -146,6 +174,107 @@ impl ChatId {
|
|||||||
self.set_blocked(context, Blocked::Not).await;
|
self.set_blocked(context, Blocked::Not).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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(
|
||||||
|
self,
|
||||||
|
context: &Context,
|
||||||
|
protect: ProtectionStatus,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ensure!(!self.is_special(), "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.", contact.get_display_name());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chattype::Undefined => bail!("Undefined group type"),
|
||||||
|
},
|
||||||
|
ProtectionStatus::Unprotected => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"UPDATE chats SET protected=? WHERE id=?;",
|
||||||
|
paramsv![protect, self],
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
context.emit_event(EventType::ChatModified(self));
|
||||||
|
|
||||||
|
// make sure, the receivers will get all keys
|
||||||
|
reset_gossiped_timestamp(context, self).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
protect: ProtectionStatus,
|
||||||
|
promoted: bool,
|
||||||
|
from_id: u32,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let msg_text = context.stock_protection_msg(protect, from_id).await;
|
||||||
|
|
||||||
|
if promoted {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets protection and sends or adds a message.
|
||||||
|
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?;
|
||||||
|
|
||||||
|
if let Err(e) = self.inner_set_protection(context, protect).await {
|
||||||
|
error!(context, "Cannot set protection: {}", e); // make error user-visible
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_protection_msg(context, protect, chat.is_promoted(), DC_CONTACT_ID_SELF)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
/// Archives or unarchives a chat.
|
/// Archives or unarchives a chat.
|
||||||
pub async fn set_visibility(
|
pub async fn set_visibility(
|
||||||
self,
|
self,
|
||||||
@@ -538,6 +667,7 @@ pub struct Chat {
|
|||||||
pub param: Params,
|
pub param: Params,
|
||||||
is_sending_locations: bool,
|
is_sending_locations: bool,
|
||||||
pub mute_duration: MuteDuration,
|
pub mute_duration: MuteDuration,
|
||||||
|
protected: ProtectionStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chat {
|
impl Chat {
|
||||||
@@ -547,7 +677,7 @@ impl Chat {
|
|||||||
.sql
|
.sql
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT c.type, c.name, c.grpid, c.param, c.archived,
|
"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
|
FROM chats c
|
||||||
WHERE c.id=?;",
|
WHERE c.id=?;",
|
||||||
paramsv![chat_id],
|
paramsv![chat_id],
|
||||||
@@ -562,6 +692,7 @@ impl Chat {
|
|||||||
blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
|
blocked: row.get::<_, Option<_>>(5)?.unwrap_or_default(),
|
||||||
is_sending_locations: row.get(6)?,
|
is_sending_locations: row.get(6)?,
|
||||||
mute_duration: row.get(7)?,
|
mute_duration: row.get(7)?,
|
||||||
|
protected: row.get(8)?,
|
||||||
};
|
};
|
||||||
Ok(c)
|
Ok(c)
|
||||||
},
|
},
|
||||||
@@ -727,9 +858,9 @@ impl Chat {
|
|||||||
!self.is_unpromoted()
|
!self.is_unpromoted()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if chat is a verified group chat.
|
/// Returns true if chat protection is enabled.
|
||||||
pub fn is_verified(&self) -> bool {
|
pub fn is_protected(&self) -> bool {
|
||||||
self.typ == Chattype::VerifiedGroup
|
self.protected == ProtectionStatus::Protected
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if location streaming is enabled in the chat.
|
/// Returns true if location streaming is enabled in the chat.
|
||||||
@@ -756,15 +887,12 @@ impl Chat {
|
|||||||
let mut to_id = 0;
|
let mut to_id = 0;
|
||||||
let mut location_id = 0;
|
let mut location_id = 0;
|
||||||
|
|
||||||
if !(self.typ == Chattype::Single
|
if !(self.typ == Chattype::Single || self.typ == Chattype::Group) {
|
||||||
|| self.typ == Chattype::Group
|
|
||||||
|| self.typ == Chattype::VerifiedGroup)
|
|
||||||
{
|
|
||||||
error!(context, "Cannot send to chat type #{}.", self.typ,);
|
error!(context, "Cannot send to chat type #{}.", self.typ,);
|
||||||
bail!("Cannot set 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
|
&& !is_contact_in_chat(context, self.id, DC_CONTACT_ID_SELF).await
|
||||||
{
|
{
|
||||||
emit_event!(
|
emit_event!(
|
||||||
@@ -781,7 +909,7 @@ impl Chat {
|
|||||||
|
|
||||||
let new_rfc724_mid = {
|
let new_rfc724_mid = {
|
||||||
let grpid = match self.typ {
|
let grpid = match self.typ {
|
||||||
Chattype::Group | Chattype::VerifiedGroup => Some(self.grpid.as_str()),
|
Chattype::Group => Some(self.grpid.as_str()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
dc_create_outgoing_rfc724_mid(grpid, &from)
|
dc_create_outgoing_rfc724_mid(grpid, &from)
|
||||||
@@ -805,7 +933,7 @@ impl Chat {
|
|||||||
);
|
);
|
||||||
bail!("Cannot set message, contact for {} not found.", self.id);
|
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
|
&& self.param.get_int(Param::Unpromoted).unwrap_or_default() == 1
|
||||||
{
|
{
|
||||||
msg.param.set_int(Param::AttachGroupImage, 1);
|
msg.param.set_int(Param::AttachGroupImage, 1);
|
||||||
@@ -1000,7 +1128,7 @@ pub struct ChatInfo {
|
|||||||
///
|
///
|
||||||
/// On the C API this number is one of the
|
/// On the C API this number is one of the
|
||||||
/// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
|
/// `DC_CHAT_TYPE_UNDEFINED`, `DC_CHAT_TYPE_SINGLE`,
|
||||||
/// `DC_CHAT_TYPE_GROUP` or `DC_CHAT_TYPE_VERIFIED_GROUP`
|
/// or `DC_CHAT_TYPE_GROUP`
|
||||||
/// constants.
|
/// constants.
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub type_: u32,
|
pub type_: u32,
|
||||||
@@ -1865,7 +1993,7 @@ pub async fn get_chat_contacts(context: &Context, chat_id: ChatId) -> Vec<u32> {
|
|||||||
|
|
||||||
pub async fn create_group_chat(
|
pub async fn create_group_chat(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
verified: VerifiedStatus,
|
protect: ProtectionStatus,
|
||||||
chat_name: impl AsRef<str>,
|
chat_name: impl AsRef<str>,
|
||||||
) -> Result<ChatId, Error> {
|
) -> Result<ChatId, Error> {
|
||||||
let chat_name = improve_single_line_input(chat_name);
|
let chat_name = improve_single_line_input(chat_name);
|
||||||
@@ -1879,11 +2007,7 @@ pub async fn create_group_chat(
|
|||||||
context.sql.execute(
|
context.sql.execute(
|
||||||
"INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);",
|
"INSERT INTO chats (type, name, grpid, param, created_timestamp) VALUES(?, ?, ?, \'U=1\', ?);",
|
||||||
paramsv![
|
paramsv![
|
||||||
if verified != VerifiedStatus::Unverified {
|
Chattype::Group,
|
||||||
Chattype::VerifiedGroup
|
|
||||||
} else {
|
|
||||||
Chattype::Group
|
|
||||||
},
|
|
||||||
chat_name,
|
chat_name,
|
||||||
grpid,
|
grpid,
|
||||||
time(),
|
time(),
|
||||||
@@ -1907,6 +2031,10 @@ pub async fn create_group_chat(
|
|||||||
chat_id: ChatId::new(0),
|
chat_id: ChatId::new(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if protect == ProtectionStatus::Protected {
|
||||||
|
chat_id.set_protection(context, protect).await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(chat_id)
|
Ok(chat_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2032,12 +2160,12 @@ pub(crate) async fn add_contact_to_chat_ex(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// else continue and send status mail
|
// else continue and send status mail
|
||||||
if chat.typ == Chattype::VerifiedGroup
|
if chat.is_protected()
|
||||||
&& contact.is_verified(context).await != VerifiedStatus::BidirectVerified
|
&& contact.is_verified(context).await != VerifiedStatus::BidirectVerified
|
||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
context,
|
context,
|
||||||
"Only bidirectional verified contacts can be added to verified groups."
|
"Only bidirectional verified contacts can be added to protected chats."
|
||||||
);
|
);
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
@@ -2075,7 +2203,7 @@ async fn real_group_exists(context: &Context, chat_id: ChatId) -> bool {
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.exists(
|
.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],
|
paramsv![chat_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -2601,7 +2729,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(
|
pub(crate) async fn get_chat_id_by_grpid(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
grpid: impl AsRef<str>,
|
grpid: impl AsRef<str>,
|
||||||
@@ -2609,14 +2737,16 @@ pub(crate) async fn get_chat_id_by_grpid(
|
|||||||
context
|
context
|
||||||
.sql
|
.sql
|
||||||
.query_row(
|
.query_row(
|
||||||
"SELECT id, blocked, type FROM chats WHERE grpid=?;",
|
"SELECT id, blocked, protected FROM chats WHERE grpid=?;",
|
||||||
paramsv![grpid.as_ref()],
|
paramsv![grpid.as_ref()],
|
||||||
|row| {
|
|row| {
|
||||||
let chat_id = row.get::<_, ChatId>(0)?;
|
let chat_id = row.get::<_, ChatId>(0)?;
|
||||||
|
|
||||||
let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
|
let b = row.get::<_, Option<Blocked>>(1)?.unwrap_or_default();
|
||||||
let v = row.get::<_, Option<Chattype>>(2)?.unwrap_or_default();
|
let p = row
|
||||||
Ok((chat_id, v == Chattype::VerifiedGroup, b))
|
.get::<_, Option<ProtectionStatus>>(2)?
|
||||||
|
.unwrap_or_default();
|
||||||
|
Ok((chat_id, p == ProtectionStatus::Protected, b))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -2898,7 +3028,7 @@ mod tests {
|
|||||||
async fn test_add_contact_to_chat_ex_add_self() {
|
async fn test_add_contact_to_chat_ex_add_self() {
|
||||||
// Adding self to a contact should succeed, even though it's pointless.
|
// Adding self to a contact should succeed, even though it's pointless.
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false)
|
let added = add_contact_to_chat_ex(&t.ctx, chat_id, DC_CONTACT_ID_SELF, false)
|
||||||
@@ -3284,7 +3414,7 @@ mod tests {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
async_std::task::sleep(std::time::Duration::from_millis(1000)).await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -3329,7 +3459,7 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_set_chat_name() {
|
async fn test_set_chat_name() {
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -3372,7 +3502,7 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_shall_attach_selfavatar() {
|
async fn test_shall_attach_selfavatar() {
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap());
|
assert!(!shall_attach_selfavatar(&t.ctx, chat_id).await.unwrap());
|
||||||
@@ -3396,7 +3526,7 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_set_mute_duration() {
|
async fn test_set_mute_duration() {
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// Initial
|
// Initial
|
||||||
|
|||||||
@@ -362,9 +362,7 @@ impl Chatlist {
|
|||||||
let mut lastcontact = None;
|
let mut lastcontact = None;
|
||||||
|
|
||||||
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await {
|
let lastmsg = if let Ok(lastmsg) = Message::load_from_db(context, lastmsg_id).await {
|
||||||
if lastmsg.from_id != DC_CONTACT_ID_SELF
|
if lastmsg.from_id != DC_CONTACT_ID_SELF && chat.typ == Chattype::Group {
|
||||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
|
||||||
{
|
|
||||||
lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok();
|
lastcontact = Contact::load_from_db(context, lastmsg.from_id).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -440,13 +438,13 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_try_load() {
|
async fn test_try_load() {
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.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
|
.await
|
||||||
.unwrap();
|
.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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -489,7 +487,7 @@ mod tests {
|
|||||||
async fn test_sort_self_talk_up_on_forward() {
|
async fn test_sort_self_talk_up_on_forward() {
|
||||||
let t = TestContext::new().await;
|
let t = TestContext::new().await;
|
||||||
t.ctx.update_device_chats().await.unwrap();
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -546,7 +544,7 @@ mod tests {
|
|||||||
#[async_std::test]
|
#[async_std::test]
|
||||||
async fn test_get_summary_unwrap() {
|
async fn test_get_summary_unwrap() {
|
||||||
let t = TestContext::new().await;
|
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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|||||||
@@ -145,7 +145,6 @@ pub enum Chattype {
|
|||||||
Undefined = 0,
|
Undefined = 0,
|
||||||
Single = 100,
|
Single = 100,
|
||||||
Group = 120,
|
Group = 120,
|
||||||
VerifiedGroup = 130,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Chattype {
|
impl Default for Chattype {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use sha2::{Digest, Sha256};
|
|||||||
|
|
||||||
use mailparse::SingleInfo;
|
use mailparse::SingleInfo;
|
||||||
|
|
||||||
use crate::chat::{self, Chat, ChatId};
|
use crate::chat::{self, Chat, ChatId, ProtectionStatus};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
@@ -714,6 +714,19 @@ async fn add_parts(
|
|||||||
ephemeral_timer = EphemeralTimer::Disabled;
|
ephemeral_timer = EphemeralTimer::Disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// change chat protection
|
||||||
|
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(new_status, from_id).await,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// correct message_timestamp, it should not be used before,
|
// correct message_timestamp, it should not be used before,
|
||||||
// however, we cannot do this earlier as we need from_id to be set
|
// however, we cannot do this earlier as we need from_id to be set
|
||||||
let in_fresh = state == MessageState::InFresh;
|
let in_fresh = state == MessageState::InFresh;
|
||||||
@@ -1199,7 +1212,7 @@ async fn create_or_lookup_group(
|
|||||||
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
|
|| X_MrAddToGrp.is_some() && addr_cmp(&self_addr, X_MrAddToGrp.as_ref().unwrap()))
|
||||||
{
|
{
|
||||||
// group does not exist but should be created
|
// 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) =
|
if let Err(err) =
|
||||||
check_verified_properties(context, mime_parser, from_id as u32, to_ids).await
|
check_verified_properties(context, mime_parser, from_id as u32, to_ids).await
|
||||||
{
|
{
|
||||||
@@ -1207,9 +1220,9 @@ async fn create_or_lookup_group(
|
|||||||
let s = format!("{}. See 'Info' for more details", err);
|
let s = format!("{}. See 'Info' for more details", err);
|
||||||
mime_parser.repl_msg_by_error(&s);
|
mime_parser.repl_msg_by_error(&s);
|
||||||
}
|
}
|
||||||
VerifiedStatus::Verified
|
ProtectionStatus::Protected
|
||||||
} else {
|
} else {
|
||||||
VerifiedStatus::Unverified
|
ProtectionStatus::Unprotected
|
||||||
};
|
};
|
||||||
|
|
||||||
if !allow_creation {
|
if !allow_creation {
|
||||||
@@ -1222,11 +1235,17 @@ async fn create_or_lookup_group(
|
|||||||
&grpid,
|
&grpid,
|
||||||
grpname.as_ref().unwrap(),
|
grpname.as_ref().unwrap(),
|
||||||
create_blocked,
|
create_blocked,
|
||||||
create_verified,
|
create_protected,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
chat_id_blocked = create_blocked;
|
chat_id_blocked = create_blocked;
|
||||||
recreate_member_list = true;
|
recreate_member_list = true;
|
||||||
|
|
||||||
|
if create_protected == ProtectionStatus::Protected {
|
||||||
|
chat_id
|
||||||
|
.add_protection_msg(context, ProtectionStatus::Protected, false, from_id)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// again, check chat_id
|
// again, check chat_id
|
||||||
@@ -1278,7 +1297,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 {
|
if let Some(avatar_action) = &mime_parser.group_avatar {
|
||||||
info!(context, "group-avatar change for {}", chat_id);
|
info!(context, "group-avatar change for {}", chat_id);
|
||||||
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
|
if let Ok(mut chat) = Chat::load_from_db(context, chat_id).await {
|
||||||
@@ -1463,7 +1490,7 @@ async fn create_or_lookup_adhoc_group(
|
|||||||
&grpid,
|
&grpid,
|
||||||
grpname,
|
grpname,
|
||||||
create_blocked,
|
create_blocked,
|
||||||
VerifiedStatus::Unverified,
|
ProtectionStatus::Unprotected,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
for &member_id in &member_ids {
|
for &member_id in &member_ids {
|
||||||
@@ -1480,20 +1507,17 @@ async fn create_group_record(
|
|||||||
grpid: impl AsRef<str>,
|
grpid: impl AsRef<str>,
|
||||||
grpname: impl AsRef<str>,
|
grpname: impl AsRef<str>,
|
||||||
create_blocked: Blocked,
|
create_blocked: Blocked,
|
||||||
create_verified: VerifiedStatus,
|
create_protected: ProtectionStatus,
|
||||||
) -> ChatId {
|
) -> ChatId {
|
||||||
if context.sql.execute(
|
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![
|
paramsv![
|
||||||
if VerifiedStatus::Unverified != create_verified {
|
Chattype::Group,
|
||||||
Chattype::VerifiedGroup
|
|
||||||
} else {
|
|
||||||
Chattype::Group
|
|
||||||
},
|
|
||||||
grpname.as_ref(),
|
grpname.as_ref(),
|
||||||
grpid.as_ref(),
|
grpid.as_ref(),
|
||||||
create_blocked,
|
create_blocked,
|
||||||
time(),
|
time(),
|
||||||
|
create_protected,
|
||||||
],
|
],
|
||||||
).await
|
).await
|
||||||
.is_err()
|
.is_err()
|
||||||
@@ -1645,6 +1669,11 @@ async fn check_verified_properties(
|
|||||||
|
|
||||||
ensure!(mimeparser.was_encrypted(), "This message is not encrypted.");
|
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
|
// ensure, the contact is verified
|
||||||
// and the message is signed with a verified key of the sender.
|
// 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
|
// this check is skipped for SELF as there is no proper SELF-peerstate
|
||||||
@@ -1734,7 +1763,7 @@ async fn check_verified_properties(
|
|||||||
}
|
}
|
||||||
if !is_verified {
|
if !is_verified {
|
||||||
bail!(
|
bail!(
|
||||||
"{} is not a member of this verified group",
|
"{} is not a member of this protected chat",
|
||||||
to_addr.to_string()
|
to_addr.to_string()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2182,7 +2211,7 @@ mod tests {
|
|||||||
assert!(one2one.get_visibility() == ChatVisibility::Archived);
|
assert!(one2one.get_visibility() == ChatVisibility::Archived);
|
||||||
|
|
||||||
// create a group with bob, archive group
|
// 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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await;
|
chat::add_contact_to_chat(&t.ctx, group_id, bob_id).await;
|
||||||
|
|||||||
@@ -544,9 +544,7 @@ impl Message {
|
|||||||
return ret;
|
return ret;
|
||||||
};
|
};
|
||||||
|
|
||||||
let contact = if self.from_id != DC_CONTACT_ID_SELF as u32
|
let contact = if self.from_id != DC_CONTACT_ID_SELF as u32 && chat.typ == Chattype::Group {
|
||||||
&& (chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup)
|
|
||||||
{
|
|
||||||
Contact::get_by_id(context, self.from_id).await.ok()
|
Contact::get_by_id(context, self.from_id).await.ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -591,6 +589,10 @@ impl Message {
|
|||||||
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|
|| cmd != SystemMessage::Unknown && cmd != SystemMessage::AutocryptSetupMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_info_type(&self) -> SystemMessage {
|
||||||
|
self.param.get_cmd()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_system_message(&self) -> bool {
|
pub fn is_system_message(&self) -> bool {
|
||||||
let cmd = self.param.get_cmd();
|
let cmd = self.param.get_cmd();
|
||||||
cmd != SystemMessage::Unknown
|
cmd != SystemMessage::Unknown
|
||||||
@@ -976,7 +978,7 @@ impl Lot {
|
|||||||
);
|
);
|
||||||
self.text1_meaning = Meaning::Text1Self;
|
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() {
|
if msg.is_info() || contact.is_none() {
|
||||||
self.text1 = None;
|
self.text1 = None;
|
||||||
self.text1_meaning = Meaning::None;
|
self.text1_meaning = Meaning::None;
|
||||||
@@ -1661,7 +1663,7 @@ pub(crate) async fn handle_ndn(
|
|||||||
if let Ok((msg_id, chat_id, chat_type)) = res {
|
if let Ok((msg_id, chat_id, chat_type)) = res {
|
||||||
set_msg_failed(context, msg_id, error).await;
|
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 {
|
if let Some(failed_recipient) = &failed.failed_recipient {
|
||||||
let contact_id =
|
let contact_id =
|
||||||
Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown).await;
|
Contact::lookup_id_by_addr(context, failed_recipient, Origin::Unknown).await;
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
fn is_e2ee_guaranteed(&self) -> bool {
|
fn is_e2ee_guaranteed(&self) -> bool {
|
||||||
match &self.loaded {
|
match &self.loaded {
|
||||||
Loaded::Message { chat } => {
|
Loaded::Message { chat } => {
|
||||||
if chat.typ == Chattype::VerifiedGroup {
|
if chat.is_protected() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
fn min_verified(&self) -> PeerstateVerifiedStatus {
|
fn min_verified(&self) -> PeerstateVerifiedStatus {
|
||||||
match &self.loaded {
|
match &self.loaded {
|
||||||
Loaded::Message { chat } => {
|
Loaded::Message { chat } => {
|
||||||
if chat.typ == Chattype::VerifiedGroup {
|
if chat.is_protected() {
|
||||||
PeerstateVerifiedStatus::BidirectVerified
|
PeerstateVerifiedStatus::BidirectVerified
|
||||||
} else {
|
} else {
|
||||||
PeerstateVerifiedStatus::Unverified
|
PeerstateVerifiedStatus::Unverified
|
||||||
@@ -268,7 +268,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
fn should_force_plaintext(&self) -> bool {
|
fn should_force_plaintext(&self) -> bool {
|
||||||
match &self.loaded {
|
match &self.loaded {
|
||||||
Loaded::Message { chat } => {
|
Loaded::Message { chat } => {
|
||||||
if chat.typ == Chattype::VerifiedGroup {
|
if chat.is_protected() {
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
self.msg
|
self.msg
|
||||||
@@ -345,7 +345,7 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
.stock_str(StockMessage::AcSetupMsgSubject)
|
.stock_str(StockMessage::AcSetupMsgSubject)
|
||||||
.await
|
.await
|
||||||
.into_owned()
|
.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() {
|
let re = if self.in_reply_to.is_empty() {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
@@ -708,11 +708,11 @@ impl<'a, 'b> MimeFactory<'a, 'b> {
|
|||||||
let mut placeholdertext = None;
|
let mut placeholdertext = None;
|
||||||
let mut meta_part = 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()));
|
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()));
|
protected_headers.push(Header::new("Chat-Group-ID".into(), chat.grpid.clone()));
|
||||||
|
|
||||||
let encoded = encode_words(&chat.name);
|
let encoded = encode_words(&chat.name);
|
||||||
@@ -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(),
|
||||||
|
));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ pub enum SystemMessage {
|
|||||||
|
|
||||||
/// Chat ephemeral message timer is changed.
|
/// Chat ephemeral message timer is changed.
|
||||||
EphemeralTimerChanged = 10,
|
EphemeralTimerChanged = 10,
|
||||||
|
|
||||||
|
// Chat protection state changed
|
||||||
|
ChatProtectionEnabled = 11,
|
||||||
|
ChatProtectionDisabled = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SystemMessage {
|
impl Default for SystemMessage {
|
||||||
@@ -123,6 +127,7 @@ impl MimeMessage {
|
|||||||
|
|
||||||
// remove headers that are allowed _only_ in the encrypted part
|
// remove headers that are allowed _only_ in the encrypted part
|
||||||
headers.remove("secure-join-fingerprint");
|
headers.remove("secure-join-fingerprint");
|
||||||
|
headers.remove("chat-verified");
|
||||||
|
|
||||||
// Memory location for a possible decrypted message.
|
// Memory location for a possible decrypted message.
|
||||||
let mail_raw;
|
let mail_raw;
|
||||||
@@ -253,6 +258,10 @@ impl MimeMessage {
|
|||||||
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
self.is_system_message = SystemMessage::LocationStreamingEnabled;
|
||||||
} else if value == "ephemeral-timer-changed" {
|
} else if value == "ephemeral-timer-changed" {
|
||||||
self.is_system_message = SystemMessage::EphemeralTimerChanged;
|
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(())
|
Ok(())
|
||||||
@@ -848,6 +857,7 @@ impl MimeMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn repl_msg_by_error(&mut self, error_msg: impl AsRef<str>) {
|
pub fn repl_msg_by_error(&mut self, error_msg: impl AsRef<str>) {
|
||||||
|
self.is_system_message = SystemMessage::Unknown;
|
||||||
if let Some(part) = self.parts.first_mut() {
|
if let Some(part) = self.parts.first_mut() {
|
||||||
part.typ = Viewtype::Text;
|
part.typ = Viewtype::Text;
|
||||||
part.msg = format!("[{}]", error_msg.as_ref());
|
part.msg = format!("[{}]", error_msg.as_ref());
|
||||||
|
|||||||
@@ -348,7 +348,7 @@ async fn securejoin(context: &Context, qr: &str) -> Result<ChatId, JoinError> {
|
|||||||
let bob = context.bob.read().await;
|
let bob = context.bob.read().await;
|
||||||
let grpid = bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap();
|
let grpid = bob.qr_scan.as_ref().unwrap().text2.as_ref().unwrap();
|
||||||
match chat::get_chat_id_by_grpid(context, grpid).await {
|
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) => {
|
Err(err) => {
|
||||||
if start.elapsed() > Duration::from_secs(7) {
|
if start.elapsed() > Duration::from_secs(7) {
|
||||||
return Err(JoinError::MissingChat(err));
|
return Err(JoinError::MissingChat(err));
|
||||||
@@ -791,19 +791,19 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
|
|
||||||
let vg_expect_encrypted = if join_vg {
|
let vg_expect_encrypted = if join_vg {
|
||||||
let group_id = get_qr_attr!(context, text2).to_string();
|
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
|
// false since the group is created by receive_imf by
|
||||||
// the very handshake message we're handling now. But
|
// the very handshake message we're handling now. But
|
||||||
// only after we have returned. It does not impact
|
// only after we have returned. It does not impact
|
||||||
// the security invariants of secure-join however.
|
// 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
|
.await
|
||||||
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
.unwrap_or((ChatId::new(0), false, Blocked::Not));
|
||||||
// when joining a non-verified group
|
// when joining a non-verified group
|
||||||
// the vg-member-added message may be unencrypted
|
// the vg-member-added message may be unencrypted
|
||||||
// when not all group members have keys or prefer encryption.
|
// when not all group members have keys or prefer encryption.
|
||||||
// So only expect encryption if this is a verified group
|
// So only expect encryption if this is a verified group
|
||||||
is_verified_group
|
is_protected_group
|
||||||
} else {
|
} else {
|
||||||
// setup contact is always encrypted
|
// setup contact is always encrypted
|
||||||
true
|
true
|
||||||
@@ -1102,6 +1102,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
|
use crate::chat::ProtectionStatus;
|
||||||
use crate::peerstate::Peerstate;
|
use crate::peerstate::Peerstate;
|
||||||
use crate::test_utils::TestContext;
|
use crate::test_utils::TestContext;
|
||||||
|
|
||||||
@@ -1328,7 +1329,7 @@ mod tests {
|
|||||||
let alice = TestContext::new_alice().await;
|
let alice = TestContext::new_alice().await;
|
||||||
let bob = TestContext::new_bob().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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
@@ -1427,6 +1428,6 @@ mod tests {
|
|||||||
|
|
||||||
let bob_chatid = joiner.await;
|
let bob_chatid = joiner.await;
|
||||||
let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await.unwrap();
|
let bob_chat = Chat::load_from_db(&bob.ctx, bob_chatid).await.unwrap();
|
||||||
assert!(bob_chat.is_verified());
|
assert!(bob_chat.is_protected());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/sql.rs
14
src/sql.rs
@@ -1368,6 +1368,20 @@ CREATE INDEX devmsglabels_index1 ON devmsglabels (label);
|
|||||||
.await?;
|
.await?;
|
||||||
sql.set_raw_config_int(context, "dbversion", 68).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
|
// (2) updates that require high-level objects
|
||||||
// (the structure is complete now and all objects are usable)
|
// (the structure is complete now and all objects are usable)
|
||||||
|
|||||||
21
src/stock.rs
21
src/stock.rs
@@ -7,6 +7,7 @@ use strum_macros::EnumProperty;
|
|||||||
|
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat;
|
use crate::chat;
|
||||||
|
use crate::chat::ProtectionStatus;
|
||||||
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
|
use crate::constants::{Viewtype, DC_CONTACT_ID_SELF};
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
@@ -233,6 +234,12 @@ pub enum StockMessage {
|
|||||||
fallback = "Could not find your mail server.\n\nPlease check your internet connection."
|
fallback = "Could not find your mail server.\n\nPlease check your internet connection."
|
||||||
))]
|
))]
|
||||||
ErrorNoNetwork = 87,
|
ErrorNoNetwork = 87,
|
||||||
|
|
||||||
|
#[strum(props(fallback = "Chat protection enabled."))]
|
||||||
|
ProtectionEnabled = 88,
|
||||||
|
|
||||||
|
#[strum(props(fallback = "Chat protection disabled."))]
|
||||||
|
ProtectionDisabled = 89,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -398,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> {
|
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.
|
// 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
|
// this is worthwhile as this function is typically called
|
||||||
|
|||||||
Reference in New Issue
Block a user