Alla Reinsch преди 7 години
родител
ревизия
d1fb64a536
променени са 53 файла, в които са добавени 2104 реда и са изтрити 1052 реда
  1. 6 0
      deltachat-ios.xcodeproj/project.pbxproj
  2. 1 1
      deltachat-ios/ChatListController.swift
  3. 1 1
      deltachat-ios/ContactViewController.swift
  4. 1 1
      deltachat-ios/Utils.swift
  5. 3 0
      deltachat-ios/deltachat-ios-Bridging-Header.h
  6. 72 16
      deltachat-ios/libraries/deltachat-core/cmdline/cmdline.c
  7. 20 11
      deltachat-ios/libraries/deltachat-core/cmdline/main.c
  8. 63 16
      deltachat-ios/libraries/deltachat-core/cmdline/stress.c
  9. 3 0
      deltachat-ios/libraries/deltachat-core/deltachat-core.cbp
  10. 104 104
      deltachat-ios/libraries/deltachat-core/libs/libetpan/src/low-level/nntp/newsnntp.c
  11. 1 0
      deltachat-ios/libraries/deltachat-core/src/meson.build
  12. 144 60
      deltachat-ios/libraries/deltachat-core/src/mrapeerstate.c
  13. 18 8
      deltachat-ios/libraries/deltachat-core/src/mrapeerstate.h
  14. 2 2
      deltachat-ios/libraries/deltachat-core/src/mrarray.c
  15. 7 2
      deltachat-ios/libraries/deltachat-core/src/mrchat-private.h
  16. 45 28
      deltachat-ios/libraries/deltachat-core/src/mrchat.c
  17. 6 3
      deltachat-ios/libraries/deltachat-core/src/mrchat.h
  18. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrchatlist-private.h
  19. 21 4
      deltachat-ios/libraries/deltachat-core/src/mrchatlist.c
  20. 2 0
      deltachat-ios/libraries/deltachat-core/src/mrcontact-private.h
  21. 67 24
      deltachat-ios/libraries/deltachat-core/src/mrcontact.c
  22. 8 7
      deltachat-ios/libraries/deltachat-core/src/mrcontact.h
  23. 40 2
      deltachat-ios/libraries/deltachat-core/src/mrevent.h
  24. 2 0
      deltachat-ios/libraries/deltachat-core/src/mrhash.h
  25. 23 8
      deltachat-ios/libraries/deltachat-core/src/mrkey.c
  26. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrkey.h
  27. 2 2
      deltachat-ios/libraries/deltachat-core/src/mrlot-private.h
  28. 15 19
      deltachat-ios/libraries/deltachat-core/src/mrlot.c
  29. 19 6
      deltachat-ios/libraries/deltachat-core/src/mrmailbox-private.h
  30. 262 177
      deltachat-ios/libraries/deltachat-core/src/mrmailbox.c
  31. 12 6
      deltachat-ios/libraries/deltachat-core/src/mrmailbox.h
  32. 102 49
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_e2ee.c
  33. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_imex.c
  34. 3 2
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_keyhistory.c
  35. 48 18
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_qr.c
  36. 161 96
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_receive_imf.c
  37. 397 208
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_securejoin.c
  38. 75 26
      deltachat-ios/libraries/deltachat-core/src/mrmimefactory.c
  39. 105 63
      deltachat-ios/libraries/deltachat-core/src/mrmimeparser.c
  40. 7 4
      deltachat-ios/libraries/deltachat-core/src/mrmimeparser.h
  41. 35 16
      deltachat-ios/libraries/deltachat-core/src/mrmsg.c
  42. 2 1
      deltachat-ios/libraries/deltachat-core/src/mrparam.h
  43. 30 28
      deltachat-ios/libraries/deltachat-core/src/mrpgp.c
  44. 2 4
      deltachat-ios/libraries/deltachat-core/src/mrpgp.h
  45. 2 2
      deltachat-ios/libraries/deltachat-core/src/mrsimplify.c
  46. 34 9
      deltachat-ios/libraries/deltachat-core/src/mrsqlite3.c
  47. 9 1
      deltachat-ios/libraries/deltachat-core/src/mrsqlite3.h
  48. 3 5
      deltachat-ios/libraries/deltachat-core/src/mrstock.c
  49. 3 5
      deltachat-ios/libraries/deltachat-core/src/mrstock.h
  50. 7 3
      deltachat-ios/libraries/deltachat-core/src/mrtools.c
  51. 3 1
      deltachat-ios/libraries/deltachat-core/src/mrtools.h
  52. 66 0
      deltachat-ios/libraries/deltachat-core/src/mruudecode.c
  53. 37 0
      deltachat-ios/libraries/deltachat-core/src/mruudecode.h

+ 6 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -15,6 +15,7 @@
 		70A4C0A0208007B700D577B3 /* mrlot.c in Sources */ = {isa = PBXBuildFile; fileRef = 70A4C094208007B600D577B3 /* mrlot.c */; };
 		70A4C0A1208007B700D577B3 /* mrmailbox_keyhistory.c in Sources */ = {isa = PBXBuildFile; fileRef = 70A4C095208007B700D577B3 /* mrmailbox_keyhistory.c */; };
 		70B8882E2091B8550074812E /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B8882D2091B8550074812E /* ContactCell.swift */; };
+		70CC9CC120A0F92E00B25403 /* mruudecode.c in Sources */ = {isa = PBXBuildFile; fileRef = 70CC9CC020A0F92D00B25403 /* mruudecode.c */; };
 		7A0052A11FBC50C40048C3BF /* CredentialsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052A01FBC50C40048C3BF /* CredentialsController.swift */; };
 		7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052C71FBE6CB40048C3BF /* NewContactController.swift */; };
 		7A451D941FB1B1DB00177250 /* wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 7A451D921FB1B1DB00177250 /* wrapper.c */; };
@@ -131,6 +132,8 @@
 		70A4C099208007B700D577B3 /* mrchat-private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "mrchat-private.h"; sourceTree = "<group>"; };
 		70A4C09A208007B700D577B3 /* mrlot.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mrlot.h; sourceTree = "<group>"; };
 		70B8882D2091B8550074812E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
+		70CC9CBA20A0F1C500B25403 /* mruudecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mruudecode.h; sourceTree = "<group>"; };
+		70CC9CC020A0F92D00B25403 /* mruudecode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = mruudecode.c; sourceTree = "<group>"; };
 		7A0052A01FBC50C40048C3BF /* CredentialsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsController.swift; sourceTree = "<group>"; };
 		7A0052C71FBE6CB40048C3BF /* NewContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewContactController.swift; sourceTree = "<group>"; };
 		7A451D921FB1B1DB00177250 /* wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wrapper.c; sourceTree = "<group>"; };
@@ -464,6 +467,8 @@
 				70A4C091208007B600D577B3 /* mrcontact-private.h */,
 				70A4C088208007B500D577B3 /* mrcontact.h */,
 				70A4C084208007B500D577B3 /* mrerror.h */,
+				70CC9CBA20A0F1C500B25403 /* mruudecode.h */,
+				70CC9CC020A0F92D00B25403 /* mruudecode.c */,
 				70A4C083208007B400D577B3 /* mrevent.h */,
 				70A4C08E208007B600D577B3 /* mrhash.c */,
 				70A4C096208007B700D577B3 /* mrhash.h */,
@@ -649,6 +654,7 @@
 				7A9FB5511FB08557001FEA36 /* mrsaxparser.c in Sources */,
 				7A9FB54B1FB08557001FEA36 /* mrmimeparser.c in Sources */,
 				7A9FB5481FB08557001FEA36 /* mrmailbox_log.c in Sources */,
+				70CC9CC120A0F92E00B25403 /* mruudecode.c in Sources */,
 				7A79236B1FB0A2C800BC2DE5 /* create.c in Sources */,
 				7A9FB5421FB08557001FEA36 /* mrkeyring.c in Sources */,
 				AEACE2DF1FB3246400DCDD78 /* Message.swift in Sources */,

+ 1 - 1
deltachat-ios/ChatListController.swift

@@ -21,7 +21,7 @@ class ChatListController: UIViewController {
     var incomingMsgObserver: Any?
     
     func getChatList() {
-        guard let chatlistPointer = mrmailbox_get_chatlist(mailboxPointer, 0, nil) else {
+        guard let chatlistPointer = mrmailbox_get_chatlist(mailboxPointer, 0, nil, 0) else {
             fatalError("chatlistPointer was nil")
         }
         // ownership of chatlistPointer transferred here to ChatList object

+ 1 - 1
deltachat-ios/ContactViewController.swift

@@ -26,7 +26,7 @@ class ContactViewController: UIViewController {
     }
     
     override func viewWillAppear(_ animated: Bool) {
-        let c_contacts = mrmailbox_get_known_contacts(mailboxPointer, nil)
+        let c_contacts = mrmailbox_get_contacts(mailboxPointer, 0, nil)
         self.contactIds = Utils.copyAndFreeArray(inputArray: c_contacts)
         contactTableDataSource.contacts = self.contactIds
         contactTable.reloadData()

+ 1 - 1
deltachat-ios/Utils.swift

@@ -20,7 +20,7 @@ struct Utils {
     }
     
     static func getContactIds() -> [Int] {
-        let c_contacts = mrmailbox_get_known_contacts(mailboxPointer, nil)
+        let c_contacts = mrmailbox_get_contacts(mailboxPointer, 0, nil)
         return Utils.copyAndFreeArray(inputArray: c_contacts)
     }
 

+ 3 - 0
deltachat-ios/deltachat-ios-Bridging-Header.h

@@ -2,6 +2,9 @@
 //  Use this file to import your target's public headers that you would like to expose to Swift.
 //
 
+#include "mrsqlite3.h"
+#include "mrhash.h"
+#include "mrapeerstate.h"
 #include "mrmailbox.h"
 #include "mrmailbox-private.h"
 #include "mrcontact-private.h"

+ 72 - 16
deltachat-ios/libraries/deltachat-core/cmdline/cmdline.c

@@ -347,8 +347,9 @@ static void log_contactlist(mrmailbox_t* mailbox, mrarray_t* contacts)
 		if( (contact=mrmailbox_get_contact(mailbox, contact_id))!=NULL ) {
 			char* name = mrcontact_get_name(contact);
 			char* addr = mrcontact_get_addr(contact);
-			const char* verified = mrcontact_is_verified(contact)? " √": "";
-			line = mr_mprintf("%s%s <%s>", (name&&name[0])? name : "<name unset>", verified, (addr&&addr[0])? addr : "addr unset");
+			int verified_state = mrcontact_is_verified(contact);
+			const char* verified_str = verified_state? (verified_state==2? " √√":" √"): "";
+			line = mr_mprintf("%s%s <%s>", (name&&name[0])? name : "<name unset>", verified_str, (addr&&addr[0])? addr : "addr unset");
 			mrsqlite3_lock(mailbox->m_sql);
 				int peerstate_ok = mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, addr);
 			mrsqlite3_unlock(mailbox->m_sql);
@@ -388,6 +389,14 @@ void mrmailbox_cmdline_skip_auth()
 }
 
 
+static const char* chat_prefix(const mrchat_t* chat)
+{
+	     if( chat->m_type == MR_CHAT_TYPE_GROUP ) { return "Group"; }
+	else if( chat->m_type == MR_CHAT_TYPE_VERIFIED_GROUP ) { return "VerifiedGroup"; }
+	else { return "Single"; }
+}
+
+
 char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 {
 	#define      COMMAND_FAILED    ((char*)1)
@@ -452,6 +461,7 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 				"createchat <contact-id>\n"
 				"createchatbymsg <msg-id>\n"
 				"creategroup <name>\n"
+				"createverified <name>\n"
 				"addmember <contact-id>\n"
 				"removemember <contact-id>\n"
 				"groupname <name>\n"
@@ -476,12 +486,13 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 				"delmsg <msg-id>\n"
 				"===========================Contact commands==\n"
 				"listcontacts [<query>]\n"
+				"listverified [<query>]\n"
 				"addcontact [<name>] <addr>\n"
 				"contactinfo <contact-id>\n"
 				"delcontact <contact-id>\n"
 				"cleanupcontacts\n"
 				"======================================Misc.==\n"
-				"getqr\n"
+				"getqr [<chat-id>]\n"
 				"getbadqr\n"
 				"checkqr <qr-contenct>\n"
 				"event <event-id to test>\n"
@@ -693,7 +704,7 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 	else if( strcmp(cmd, "listchats")==0 || strcmp(cmd, "listarchived")==0 || strcmp(cmd, "chats")==0 )
 	{
 		int listflags = strcmp(cmd, "listarchived")==0? MR_GCL_ARCHIVED_ONLY : 0;
-		mrchatlist_t* chatlist = mrmailbox_get_chatlist(mailbox, listflags, arg1);
+		mrchatlist_t* chatlist = mrmailbox_get_chatlist(mailbox, listflags, arg1, 0);
 		if( chatlist ) {
 			int i, cnt = mrchatlist_get_cnt(chatlist);
 			if( cnt>0 ) {
@@ -704,9 +715,9 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 
 					char* temp_subtitle = mrchat_get_subtitle(chat);
 					char* temp_name = mrchat_get_name(chat);
-					const char* verified = mrchat_is_verified(chat)? " √": "";
-						mrmailbox_log_info(mailbox, 0, "%s#%i: %s%s [%s] [%i fresh]", mrchat_get_type(chat)==MR_CHAT_TYPE_GROUP? "Groupchat" : "Chat",
-							(int)mrchat_get_id(chat), temp_name, verified, temp_subtitle, (int)mrmailbox_get_fresh_msg_count(mailbox, mrchat_get_id(chat)));
+						mrmailbox_log_info(mailbox, 0, "%s#%i: %s [%s] [%i fresh]",
+							chat_prefix(chat),
+							(int)mrchat_get_id(chat), temp_name, temp_subtitle, (int)mrmailbox_get_fresh_msg_count(mailbox, mrchat_get_id(chat)));
 					free(temp_subtitle);
 					free(temp_name);
 
@@ -767,7 +778,7 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 			mrarray_t* msglist = mrmailbox_get_chat_msgs(mailbox, mrchat_get_id(sel_chat), MR_GCM_ADDDAYMARKER, 0);
 			char* temp2 = mrchat_get_subtitle(sel_chat);
 			char* temp_name = mrchat_get_name(sel_chat);
-				mrmailbox_log_info(mailbox, 0, "Chat#%i: %s [%s]", mrchat_get_id(sel_chat), temp_name, temp2);
+				mrmailbox_log_info(mailbox, 0, "%s#%i: %s [%s]", chat_prefix(sel_chat), mrchat_get_id(sel_chat), temp_name, temp2);
 			free(temp_name);
 			free(temp2);
 			if( msglist ) {
@@ -793,7 +804,7 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 		if( arg1 ) {
 			int contact_id = atoi(arg1);
 			int chat_id = mrmailbox_create_chat_by_contact_id(mailbox, contact_id);
-			ret = chat_id!=0? mr_mprintf("Chat#%lu created successfully.", chat_id) : COMMAND_FAILED;
+			ret = chat_id!=0? mr_mprintf("Single#%lu created successfully.", chat_id) : COMMAND_FAILED;
 		}
 		else {
 			ret = safe_strdup("ERROR: Argument <contact-id> missing.");
@@ -804,7 +815,14 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 		if( arg1 ) {
 			int msg_id = atoi(arg1);
 			int chat_id = mrmailbox_create_chat_by_msg_id(mailbox, msg_id);
-			ret = chat_id!=0? mr_mprintf("Chat#%lu created successfully.", chat_id) : COMMAND_FAILED;
+			if( chat_id != 0 ) {
+				mrchat_t* chat = mrmailbox_get_chat(mailbox, chat_id);
+					ret = mr_mprintf("%s#%lu created successfully.", chat_prefix(chat), chat_id);
+				mrchat_unref(chat);
+			}
+			else {
+				ret = COMMAND_FAILED;
+			}
 		}
 		else {
 			ret = safe_strdup("ERROR: Argument <msg-id> missing.");
@@ -813,8 +831,18 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 	else if( strcmp(cmd, "creategroup")==0 )
 	{
 		if( arg1 ) {
-			int chat_id = mrmailbox_create_group_chat(mailbox, arg1);
-			ret = chat_id!=0? mr_mprintf("Groupchat#%lu created successfully.", chat_id) : COMMAND_FAILED;
+			int chat_id = mrmailbox_create_group_chat(mailbox, 0, arg1);
+			ret = chat_id!=0? mr_mprintf("Group#%lu created successfully.", chat_id) : COMMAND_FAILED;
+		}
+		else {
+			ret = safe_strdup("ERROR: Argument <name> missing.");
+		}
+	}
+	else if( strcmp(cmd, "createverified")==0 )
+	{
+		if( arg1 ) {
+			int chat_id = mrmailbox_create_group_chat(mailbox, 1, arg1);
+			ret = chat_id!=0? mr_mprintf("VerifiedGroup#%lu created successfully.", chat_id) : COMMAND_FAILED;
 		}
 		else {
 			ret = safe_strdup("ERROR: Argument <name> missing.");
@@ -1108,9 +1136,9 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 	 * Contact commands
 	 ******************************************************************************/
 
-	else if( strcmp(cmd, "listcontacts")==0 || strcmp(cmd, "contacts")==0 )
+	else if( strcmp(cmd, "listcontacts")==0 || strcmp(cmd, "contacts")==0 || strcmp(cmd, "listverified")==0 )
 	{
-		mrarray_t* contacts = mrmailbox_get_known_contacts(mailbox, arg1);
+		mrarray_t* contacts = mrmailbox_get_contacts(mailbox, strcmp(cmd, "listverified")==0? MR_GCL_VERIFIED_ONLY : 0, arg1);
 		if( contacts ) {
 			log_contactlist(mailbox, contacts);
 			ret = mr_mprintf("%i contacts.", (int)mrarray_get_cnt(contacts));
@@ -1142,7 +1170,34 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 	{
 		if( arg1 ) {
 			int contact_id = atoi(arg1);
-			ret = mrmailbox_get_contact_encrinfo(mailbox, contact_id);
+			mrstrbuilder_t strbuilder;
+			mrstrbuilder_init(&strbuilder, 0);
+
+			mrcontact_t* contact = mrmailbox_get_contact(mailbox, contact_id);
+			char* nameNaddr = mrcontact_get_name_n_addr(contact);
+			mrstrbuilder_catf(&strbuilder, "Contact info for: %s:\n\n", nameNaddr);
+			free(nameNaddr);
+			mrcontact_unref(contact);
+
+			char* encrinfo = mrmailbox_get_contact_encrinfo(mailbox, contact_id);
+			mrstrbuilder_cat(&strbuilder, encrinfo);
+			free(encrinfo);
+
+			mrchatlist_t* chatlist = mrmailbox_get_chatlist(mailbox, 0, NULL, contact_id);
+			int chatlist_cnt = mrchatlist_get_cnt(chatlist);
+			if( chatlist_cnt > 0 ) {
+				mrstrbuilder_catf(&strbuilder, "\n\n%i chats shared with Contact#%i: ", chatlist_cnt, contact_id);
+				for( int i = 0; i < chatlist_cnt; i++ ) {
+					if( i ) { mrstrbuilder_cat(&strbuilder, ", ");  }
+
+					mrchat_t* chat = mrmailbox_get_chat(mailbox, mrchatlist_get_chat_id(chatlist, i));
+						mrstrbuilder_catf(&strbuilder, "%s#%i", chat_prefix(chat), mrchat_get_id(chat));
+					mrchat_unref(chat);
+				}
+			}
+			mrchatlist_unref(chatlist);
+
+			ret = strbuilder.m_buf;
 		}
 		else {
 			ret = safe_strdup("ERROR: Argument <contact-id> missing.");
@@ -1168,7 +1223,8 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 
 	else if( strcmp(cmd, "getqr")==0 )
 	{
-		ret = mrmailbox_get_securejoin_qr(mailbox, 0);
+		ret = mrmailbox_get_securejoin_qr(mailbox, arg1? atoi(arg1) : 0);
+		if( ret == NULL || ret[0]==0 ) { free(ret); ret = COMMAND_FAILED; }
 	}
 	else if( strcmp(cmd, "checkqr")==0 )
 	{

+ 20 - 11
deltachat-ios/libraries/deltachat-core/cmdline/main.c

@@ -51,7 +51,7 @@ static char* read_cmd(void)
 }
 
 
-static int s_do_log_info = 0;
+static int s_do_log_info = 1;
 
 
 static uintptr_t receive_event(mrmailbox_t* mailbox, int event, uintptr_t data1, uintptr_t data2)
@@ -126,6 +126,7 @@ static uintptr_t receive_event(mrmailbox_t* mailbox, int event, uintptr_t data1,
 
 int main(int argc, char ** argv)
 {
+	char*        cmd = NULL;
 	mrmailbox_t* mailbox = mrmailbox_new(receive_event, NULL, "CLI");
 
 	mrmailbox_cmdline_skip_auth(mailbox); /* disable the need to enter the command `auth <password>` for all mailboxes. */
@@ -141,16 +142,21 @@ int main(int argc, char ** argv)
 		printf("ERROR: Bad arguments\n");
 	}
 
+	s_do_log_info = 0;
 	stress_functions(mailbox);
+	s_do_log_info = 1;
 
 	printf("Delta Chat Core is awaiting your commands.\n");
-	s_do_log_info = 1;
 
 	/* wait for command */
 	while(1)
 	{
 		/* read command */
-		const char* cmd = read_cmd();
+		const char* cmdline = read_cmd();
+		free(cmd);
+		cmd = safe_strdup(cmdline);
+		char* arg1 = strchr(cmd, ' ');
+		if( arg1 ) { *arg1 = 0; arg1++; }
 
 		if( strcmp(cmd, "clear")==0 )
 		{
@@ -160,14 +166,16 @@ int main(int argc, char ** argv)
 		else if( strcmp(cmd, "getqr")==0 || strcmp(cmd, "getbadqr")==0 )
 		{
 			mrmailbox_connect(mailbox);
-			char* qrstr  = mrmailbox_get_securejoin_qr(mailbox, 0);
-			if( strcmp(cmd, "getbadqr")==0 && strlen(qrstr)>40 ) {
-				for( int i = 12; i < 22; i++ ) { qrstr[i] = '0'; }
+			char* qrstr  = mrmailbox_get_securejoin_qr(mailbox, arg1? atoi(arg1) : 0);
+			if( qrstr && qrstr[0] ) {
+				if( strcmp(cmd, "getbadqr")==0 && strlen(qrstr)>40 ) {
+					for( int i = 12; i < 22; i++ ) { qrstr[i] = '0'; }
+				}
+				printf("%s\n", qrstr);
+				char* syscmd = mr_mprintf("qrencode -t ansiutf8 \"%s\" -o -", qrstr); /* `-t ansiutf8`=use back/write, `-t utf8`=use terminal colors */
+				system(syscmd);
+				free(syscmd);
 			}
-			printf("%s\n", qrstr);
-			char* syscmd = mr_mprintf("qrencode -t ansiutf8 \"%s\" -o -", qrstr); /* `-t ansiutf8`=use back/write, `-t utf8`=use terminal colors */
-			system(syscmd);
-			free(syscmd);
 			free(qrstr);
 		}
 		else if( strcmp(cmd, "exit")==0 )
@@ -180,7 +188,7 @@ int main(int argc, char ** argv)
 		}
 		else
 		{
-			char* execute_result = mrmailbox_cmdline(mailbox, cmd);
+			char* execute_result = mrmailbox_cmdline(mailbox, cmdline);
 			if( execute_result ) {
 				printf("%s\n", execute_result);
 				free(execute_result);
@@ -188,6 +196,7 @@ int main(int argc, char ** argv)
 		}
 	}
 
+	free(cmd);
 	mrmailbox_close(mailbox);
 	mrmailbox_unref(mailbox);
 	mailbox = NULL;

+ 63 - 16
deltachat-ios/libraries/deltachat-core/cmdline/stress.c

@@ -276,6 +276,20 @@ void stress_functions(mrmailbox_t* mailbox)
 		mrmimeparser_unref(mimeparser);
 	}
 
+	/* test message helpers
+	 **************************************************************************/
+
+	{
+		int type;
+		char* mime;
+		mrmsg_guess_msgtype_from_suffix("foo/bar-sth.mp3", NULL, NULL);
+		mrmsg_guess_msgtype_from_suffix("foo/bar-sth.mp3", NULL, &mime);
+		assert( strcmp(mime, "audio/mpeg")==0 );
+		mrmsg_guess_msgtype_from_suffix("foo/bar-sth.mp3", &type, NULL);
+		assert( type == MR_MSG_AUDIO );
+		free(mime);
+	}
+
 	/* test some string functions
 	 **************************************************************************/
 
@@ -288,7 +302,7 @@ void stress_functions(mrmailbox_t* mailbox)
 
 		str = strdup("this is a little test string");
 			mr_truncate_str(str, 16);
-			assert( strcmp(str, "this is a " MR_ELLIPSE_STR)==0 );
+			assert( strcmp(str, "this is a " MR_EDITORIAL_ELLIPSE)==0 );
 		free(str);
 
 		str = strdup("1234");
@@ -343,8 +357,9 @@ void stress_functions(mrmailbox_t* mailbox)
 		assert( strcmp("mdn_rcvd="  MR_STRINGIFY(MR_STATE_OUT_MDN_RCVD),  "mdn_rcvd=28")==0 );
 
 		assert( strcmp("undefined="    MR_STRINGIFY(MR_CHAT_TYPE_UNDEFINED),      "undefined=0")==0 );
-		assert( strcmp("normal="       MR_STRINGIFY(MR_CHAT_TYPE_NORMAL),         "normal=100")==0 );
+		assert( strcmp("single="       MR_STRINGIFY(MR_CHAT_TYPE_SINGLE),         "single=100")==0 );
 		assert( strcmp("group="        MR_STRINGIFY(MR_CHAT_TYPE_GROUP),          "group=120")==0 );
+		assert( strcmp("vgroup="       MR_STRINGIFY(MR_CHAT_TYPE_VERIFIED_GROUP), "vgroup=130")==0 );
 
 		assert( strcmp("deaddrop="     MR_STRINGIFY(MR_CHAT_ID_DEADDROP),         "deaddrop=1")==0 );
 		assert( strcmp("trash="        MR_STRINGIFY(MR_CHAT_ID_TRASH),            "trash=3")==0 );
@@ -358,6 +373,10 @@ void stress_functions(mrmailbox_t* mailbox)
 
 		assert( strcmp("grpimg="    MR_STRINGIFY(MR_CMD_GROUPIMAGE_CHANGED), "grpimg=3")==0 );
 
+		assert( strcmp("notverified="    MR_STRINGIFY(MRV_NOT_VERIFIED),  "notverified=0")==0 );
+		assert( strcmp("simple="         MR_STRINGIFY(MRV_SIMPLE),        "simple=1")==0 );
+		assert( strcmp("bidirectional="  MR_STRINGIFY(MRV_BIDIRECTIONAL), "bidirectional=2")==0 );
+
 		assert( MRP_FILE == 'f' );
 		assert( MRP_WIDTH == 'w' );
 		assert( MRP_HEIGHT == 'h' );
@@ -709,46 +728,74 @@ void stress_functions(mrmailbox_t* mailbox)
 		{
 			mrkeyring_t* keyring = mrkeyring_new();
 			mrkeyring_add(keyring, private_key);
+
+			mrkeyring_t* public_keyring = mrkeyring_new();
+			mrkeyring_add(public_keyring, public_key);
+
+			mrkeyring_t* public_keyring2 = mrkeyring_new();
+			mrkeyring_add(public_keyring2, public_key2);
+
 			void* plain = NULL;
-			int validation_errors = 0, ok;
+			mrhash_t valid_signatures;
+			mrhash_init(&valid_signatures, MRHASH_STRING, 1/*copy key*/);
+			int ok;
 
-			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_key/*for validate*/, 1, &plain, &plain_bytes, &validation_errors);
+			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_keyring/*for validate*/, 1, &plain, &plain_bytes, &valid_signatures);
 			assert( ok && plain && plain_bytes>0 );
 			assert( strncmp((char*)plain, original_text, strlen(original_text))==0 );
-			assert( validation_errors == 0 );
+			assert( mrhash_count(&valid_signatures) == 1 );
 			free(plain); plain = NULL;
+			mrhash_clear(&valid_signatures);
 
-			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, NULL/*for validate*/, 1, &plain, &plain_bytes, &validation_errors);
+			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, NULL/*for validate*/, 1, &plain, &plain_bytes, &valid_signatures);
 			assert( ok && plain && plain_bytes>0 );
 			assert( strncmp((char*)plain, original_text, strlen(original_text))==0 );
-			assert( validation_errors == MR_VALIDATE_UNKNOWN_SIGNATURE );
+			assert( mrhash_count(&valid_signatures) == 0 );
 			free(plain); plain = NULL;
+			mrhash_clear(&valid_signatures);
 
-			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_key2/*for validate*/, 1, &plain, &plain_bytes, &validation_errors);
+			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_keyring2/*for validate*/, 1, &plain, &plain_bytes, &valid_signatures);
 			assert( ok && plain && plain_bytes>0 );
 			assert( strncmp((char*)plain, original_text, strlen(original_text))==0 );
-			assert( validation_errors == MR_VALIDATE_UNKNOWN_SIGNATURE );
+			assert( mrhash_count(&valid_signatures) == 0 );
 			free(plain); plain = NULL;
+			mrhash_clear(&valid_signatures);
 
-			ok = mrpgp_pk_decrypt(mailbox, ctext_unsigned, ctext_unsigned_bytes, keyring, public_key/*for validate*/, 1, &plain, &plain_bytes, &validation_errors);
+			mrkeyring_add(public_keyring2, public_key);
+			ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_keyring2/*for validate*/, 1, &plain, &plain_bytes, &valid_signatures);
 			assert( ok && plain && plain_bytes>0 );
 			assert( strncmp((char*)plain, original_text, strlen(original_text))==0 );
-			assert( validation_errors == MR_VALIDATE_NO_SIGNATURE );
+			assert( mrhash_count(&valid_signatures) == 1 );
 			free(plain); plain = NULL;
+			mrhash_clear(&valid_signatures);
+
+			ok = mrpgp_pk_decrypt(mailbox, ctext_unsigned, ctext_unsigned_bytes, keyring, public_keyring/*for validate*/, 1, &plain, &plain_bytes, &valid_signatures);
+			assert( ok && plain && plain_bytes>0 );
+			assert( strncmp((char*)plain, original_text, strlen(original_text))==0 );
+			assert( mrhash_count(&valid_signatures) == 0 );
+			free(plain); plain = NULL;
+			mrhash_clear(&valid_signatures);
 
 			mrkeyring_unref(keyring);
+			mrkeyring_unref(public_keyring);
+			mrkeyring_unref(public_keyring2);
 		}
 
 		{
 			mrkeyring_t* keyring = mrkeyring_new();
 			mrkeyring_add(keyring, private_key2);
+
+			mrkeyring_t* public_keyring = mrkeyring_new();
+			mrkeyring_add(public_keyring, public_key);
+
 			void* plain = NULL;
-			int validation_errors = 0;
-			int ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_key/*for validate*/, 1, &plain, &plain_bytes, &validation_errors);
+			int ok = mrpgp_pk_decrypt(mailbox, ctext_signed, ctext_signed_bytes, keyring, public_keyring/*for validate*/, 1, &plain, &plain_bytes, NULL);
 			assert( ok && plain && plain_bytes>0 );
 			assert( strcmp(plain, original_text)==0 );
-			mrkeyring_unref(keyring);
 			free(plain);
+
+			mrkeyring_unref(keyring);
+			mrkeyring_unref(public_keyring);
 		}
 
 		free(ctext_signed);
@@ -772,11 +819,11 @@ void stress_functions(mrmailbox_t* mailbox)
 	if( mrmailbox_is_configured(mailbox) )
 	{
 		char* qr = mrmailbox_get_securejoin_qr(mailbox, 0);
-		assert( strlen(qr)>55 && strncmp(qr, "OPENPGP4FPR:", 12)==0 && strncmp(&qr[52], "#v=", 3)==0 );
+		assert( strlen(qr)>55 && strncmp(qr, "OPENPGP4FPR:", 12)==0 && strncmp(&qr[52], "#a=", 3)==0 );
 
 		mrlot_t* res = mrmailbox_check_qr(mailbox, qr);
 		assert( res );
-		assert( res->m_state == MR_QR_ASK_SECUREJOIN || res->m_state == MR_QR_FPR_MISMATCH || res->m_state == MR_QR_FPR_WITHOUT_ADDR );
+		assert( res->m_state == MR_QR_ASK_VERIFYCONTACT || res->m_state == MR_QR_FPR_MISMATCH || res->m_state == MR_QR_FPR_WITHOUT_ADDR );
 
 		mrlot_unref(res);
 		free(qr);

+ 3 - 0
deltachat-ios/libraries/deltachat-core/deltachat-core.cbp

@@ -540,6 +540,9 @@
 		<Unit filename="src/mrtools.c">
 			<Option compilerVar="CC" />
 		</Unit>
+		<Unit filename="src/mruudecode.c">
+			<Option compilerVar="CC" />
+		</Unit>
 		<Extensions>
 			<envvars />
 			<code_completion />

+ 104 - 104
deltachat-ios/libraries/deltachat-core/libs/libetpan/src/low-level/nntp/newsnntp.c

@@ -92,7 +92,7 @@ newsnntp * newsnntp_new(size_t progr_rate, progress_function * progr_fun)
   f = malloc(sizeof(* f));
   if (f == NULL)
     goto err;
-  
+
   f->nntp_stream = NULL;
   f->nntp_readonly = FALSE;
 
@@ -110,7 +110,7 @@ newsnntp * newsnntp_new(size_t progr_rate, progress_function * progr_fun)
 	f->nntp_timeout = 0;
 	f->nntp_progress_fun = NULL;
 	f->nntp_progress_context = NULL;
-  
+
   f->nntp_logger = NULL;
   f->nntp_logger_context = NULL;
 
@@ -166,7 +166,7 @@ int newsnntp_quit(newsnntp * f)
     res = NEWSNNTP_ERROR_STREAM;
     goto close;
   }
-  
+
   response = read_line(f);
   if (response == NULL) {
     res = NEWSNNTP_ERROR_STREAM;
@@ -182,7 +182,7 @@ int newsnntp_quit(newsnntp * f)
   mailstream_close(f->nntp_stream);
 
   f->nntp_stream = NULL;
-  
+
   return res;
 }
 
@@ -286,10 +286,10 @@ static int newsnntp_get_content(newsnntp * f, char ** result,
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-    
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-    
+
   case 220:
   case 221:
   case 222:
@@ -309,7 +309,7 @@ static int newsnntp_get_content(newsnntp * f, char ** result,
         mmap_string_free(buffer);
         return NEWSNNTP_ERROR_MEMORY;
       }
-      
+
       * result = result_multiline;
       * result_len = buffer->len;
       return NEWSNNTP_NO_ERROR;
@@ -399,12 +399,12 @@ group_info_init(char * name, uint32_t first, uint32_t last, uint32_t count,
 		char type)
 {
   struct newsnntp_group_info * n;
-  
+
   n = malloc(sizeof(* n));
 
   if (n == NULL)
     return NULL;
-  
+
   n->grp_name = strdup(name);
   if (n->grp_name == NULL) {
     free(n);
@@ -456,15 +456,15 @@ int newsnntp_group(newsnntp * f, const char * groupname,
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-    
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 211:
     if (!parse_group_info(f->nntp_response, info))
       return NEWSNNTP_ERROR_INVALID_RESPONSE;
     return NEWSNNTP_NO_ERROR;
-      
+
   case 411:
     return NEWSNNTP_ERROR_NO_SUCH_NEWS_GROUP;
 
@@ -502,14 +502,14 @@ int newsnntp_list(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-    
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_groups_list(f);
     return NEWSNNTP_NO_ERROR;
-    
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -549,21 +549,21 @@ int newsnntp_post(newsnntp * f, const char * message, size_t size)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-    
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
 
   case 340:
     break;
-      
+
   case 440:
     return NEWSNNTP_ERROR_POSTING_NOT_ALLOWED;
-    
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
 
-  send_data(f, message, size); 
+  send_data(f, message, size);
 
   response = read_line(f);
   if (response == NULL)
@@ -574,10 +574,10 @@ int newsnntp_post(newsnntp * f, const char * message, size_t size)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 240:
     return NEWSNNTP_NO_ERROR;
     return 1;
@@ -625,7 +625,7 @@ int newsnntp_authinfo_username(newsnntp * f, const char * username)
 
   case 281:
     return NEWSNNTP_NO_ERROR;
-      
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -654,7 +654,7 @@ int newsnntp_authinfo_password(newsnntp * f, const char * password)
 
   case 481:
     return NEWSNNTP_ERROR_AUTHENTICATION_REJECTED;
-      
+
   case 482:
     return NEWSNNTP_ERROR_AUTHENTICATION_OUT_OF_SEQUENCE;
 
@@ -663,7 +663,7 @@ int newsnntp_authinfo_password(newsnntp * f, const char * password)
 
   case 281:
     return NEWSNNTP_NO_ERROR;
-      
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -699,15 +699,15 @@ int newsnntp_list_overview_fmt(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_headers_list(f);
     return NEWSNNTP_NO_ERROR;
 
-  case 503: 
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
 
   default:
@@ -750,7 +750,7 @@ int newsnntp_list_active(newsnntp * f, const char * wildcard, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
 
@@ -779,12 +779,12 @@ static struct newsnntp_group_time *
 group_time_new(char * group_name, time_t date, char * email)
 {
   struct newsnntp_group_time * n;
-  
+
   n = malloc(sizeof(* n));
 
   if (n == NULL)
     return NULL;
-  
+
   n->grp_name = strdup(group_name);
   if (n->grp_name == NULL) {
     free(n);
@@ -847,15 +847,15 @@ int newsnntp_list_active_times(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_group_time_list(f);
     return NEWSNNTP_NO_ERROR;
 
-  case 503: 
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
 
   default:
@@ -881,12 +881,12 @@ static struct newsnntp_distrib_value_meaning *
 distrib_value_meaning_new(char * value, char * meaning)
 {
   struct newsnntp_distrib_value_meaning * n;
-  
+
   n = malloc(sizeof(* n));
 
   if (n == NULL)
     return NULL;
-  
+
   n->dst_value = strdup(value);
   if (n->dst_value == NULL) {
     free(n);
@@ -933,7 +933,7 @@ int newsnntp_list_distribution(newsnntp * f, clist ** result)
   r = send_command(f, command);
   if (r == -1)
     return NEWSNNTP_ERROR_STREAM;
-  
+
   response = read_line(f);
   if (response == NULL)
     return NEWSNNTP_ERROR_STREAM;
@@ -943,17 +943,17 @@ int newsnntp_list_distribution(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_distrib_value_meaning_list(f);
     return NEWSNNTP_NO_ERROR;
-    
-  case 503: 
+
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
-    
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -985,7 +985,7 @@ distrib_default_value_new(uint32_t weight, char * group_pattern, char * value)
   n = malloc(sizeof(* n));
   if (n == NULL)
     return NULL;
-  
+
   n->dst_group_pattern = strdup(group_pattern);
   if (n->dst_group_pattern == NULL) {
     free(n);
@@ -1042,17 +1042,17 @@ int newsnntp_list_distrib_pats(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_distrib_default_value_list(f);
     return NEWSNNTP_NO_ERROR;
 
-  case 503: 
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
-    
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -1084,7 +1084,7 @@ group_description_new(char * group_name, char * description)
   n = malloc(sizeof(* n));
   if (n == NULL)
     return NULL;
-  
+
   n->grp_name = strdup(group_name);
   if (n->grp_name == NULL) {
     free(n);
@@ -1129,7 +1129,7 @@ int newsnntp_list_newsgroups(newsnntp * f, const char * pattern,
     snprintf(command, NNTP_STRING_SIZE, "LIST NEWSGROUPS %s\r\n", pattern);
   else
     snprintf(command, NNTP_STRING_SIZE, "LIST NEWSGROUPS\r\n");
-  
+
   r = send_command(f, command);
   if (r == -1)
     return NEWSNNTP_ERROR_STREAM;
@@ -1143,15 +1143,15 @@ int newsnntp_list_newsgroups(newsnntp * f, const char * pattern,
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_group_description_list(f);
     return NEWSNNTP_NO_ERROR;
 
-  case 503: 
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
 
   default:
@@ -1205,15 +1205,15 @@ int newsnntp_list_subscriptions(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 215:
     * result = read_subscriptions_list(f);
     return NEWSNNTP_NO_ERROR;
 
-  case 503: 
+  case 503:
     return NEWSNNTP_ERROR_PROGRAM_ERROR;
 
   default:
@@ -1271,20 +1271,20 @@ int newsnntp_listgroup(newsnntp * f, const char * group_name,
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 211:
     * result = read_articles_list(f);
     return NEWSNNTP_NO_ERROR;
-      
+
   case 412:
     return NEWSNNTP_ERROR_NO_NEWSGROUP_SELECTED;
 
-  case 502: 
+  case 502:
     return NEWSNNTP_ERROR_NO_PERMISSION;
-    
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -1310,7 +1310,7 @@ int newsnntp_mode_reader(newsnntp * f)
   int r;
 
   snprintf(command, NNTP_STRING_SIZE, "MODE READER\r\n");
-  
+
   r = send_command(f, command);
   if (r == -1)
     return NEWSNNTP_ERROR_STREAM;
@@ -1323,10 +1323,10 @@ int newsnntp_mode_reader(newsnntp * f)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 200:
   case 201:
     return NEWSNNTP_NO_ERROR;
@@ -1383,7 +1383,7 @@ int newsnntp_date(newsnntp * f, struct tm * tm)
     tm->tm_sec = atoi(second);
 
     return NEWSNNTP_NO_ERROR;
-      
+
   default:
     return NEWSNNTP_ERROR_UNEXPECTED_RESPONSE;
   }
@@ -1407,7 +1407,7 @@ static struct newsnntp_xhdr_resp_item * xhdr_resp_item_new(uint32_t article,
   n = malloc(sizeof(* n));
   if (n == NULL)
     return NULL;
-  
+
   n->hdr_value = strdup(value);
   if (n->hdr_value == NULL) {
     free(n);
@@ -1485,10 +1485,10 @@ static int newsnntp_xhdr_resp(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 221:
     * result = read_xhdr_resp_list(f);
     return NEWSNNTP_NO_ERROR;
@@ -1502,7 +1502,7 @@ static int newsnntp_xhdr_resp(newsnntp * f, clist ** result)
   case 430:
     return NEWSNNTP_ERROR_ARTICLE_NOT_FOUND;
 
-  case 502: 
+  case 502:
     return NEWSNNTP_ERROR_NO_PERMISSION;
 
   default:
@@ -1541,7 +1541,7 @@ xover_resp_item_new(uint32_t article,
   n = malloc(sizeof(* n));
   if (n == NULL)
     return NULL;
-  
+
   n->ovr_subject = strdup(subject);
   if (n->ovr_subject == NULL) {
     free(n);
@@ -1604,7 +1604,7 @@ void xover_resp_item_free(struct newsnntp_xover_resp_item * n)
     free(n->ovr_references);
   clist_foreach(n->ovr_others, (clist_func) free, NULL);
   clist_free(n->ovr_others);
-  
+
   free(n);
 }
 
@@ -1640,7 +1640,7 @@ int newsnntp_xover_single(newsnntp * f, uint32_t article,
   cur = clist_begin(list);
   item = clist_content(cur);
   clist_free(list);
-  
+
   * result = item;
 
   return r;
@@ -1674,10 +1674,10 @@ static int newsnntp_xover_resp(newsnntp * f, clist ** result)
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 224:
     * result = read_xover_resp_list(f);
     return NEWSNNTP_NO_ERROR;
@@ -1726,17 +1726,17 @@ int newsnntp_authinfo_generic(newsnntp * f, const char * authentificator,
   switch (r) {
   case 480:
     return NEWSNNTP_ERROR_REQUEST_AUTHORIZATION_USERNAME;
-      
+
   case 381:
     return NEWSNNTP_WARNING_REQUEST_AUTHORIZATION_PASSWORD;
-      
+
   case 281:
     return NEWSNNTP_NO_ERROR;
 
   case 500:
     return NEWSNNTP_ERROR_COMMAND_NOT_UNDERSTOOD;
 
-  case 501: 
+  case 501:
     return NEWSNNTP_ERROR_COMMAND_NOT_SUPPORTED;
 
   case 502:
@@ -1830,7 +1830,7 @@ static int parse_response(newsnntp * f, char * response)
     f->nntp_response = f->nntp_response_buffer->str;
   else
     f->nntp_response = NULL;
- 
+
   return code;
 }
 
@@ -1885,7 +1885,7 @@ static int parse_group_info(char * response,
     return FALSE;
 
   * result = info;
-  
+
   return TRUE;
 }
 
@@ -1908,7 +1908,7 @@ static clist * read_groups_list(newsnntp * f)
 
   while (1) {
     char * p;
-      
+
     line = read_line(f);
     if (line == NULL)
       goto free_list;
@@ -1968,13 +1968,13 @@ static clist * read_headers_list(newsnntp * f)
 
   while (1) {
     line = read_line(f);
-    
+
     if (line == NULL)
       goto free_list;
-    
+
     if (mailstream_is_end_multiline(line))
       break;
-    
+
     header = strdup(line);
     if (header == NULL)
       goto free_list;
@@ -2014,39 +2014,39 @@ static clist * read_group_time_list(newsnntp * f)
   while (1) {
     char * p;
     char * remaining;
-    
+
     line = read_line(f);
-    
+
     if (line == NULL)
       goto free_list;
 
     if (mailstream_is_end_multiline(line))
       break;
-    
+
     p = cut_token(line);
     if (p == NULL)
       continue;
-      
+
     date = strtoul(p, &remaining, 10);
 
     p = remaining;
     parse_space(&p);
 
     email = p;
-    
+
     group_name = line;
-    
+
     n = group_time_new(group_name, date, email);
     if (n == NULL)
       goto free_list;
-    
+
     r = clist_append(group_time_list, n);
     if (r < 0) {
       group_time_free(n);
       goto free_list;
     }
   }
-  
+
   return group_time_list;
 
  free_list:
@@ -2073,18 +2073,18 @@ static clist * read_distrib_value_meaning_list(newsnntp * f)
 
   while (1) {
     char * p;
-      
+
     line = read_line(f);
     if (line == NULL)
       goto free_list;
 
     if (mailstream_is_end_multiline(line))
       break;
-      
+
     p = cut_token(line);
     if (p == NULL)
       continue;
-      
+
     meaning = p;
 
     value = line;
@@ -2128,7 +2128,7 @@ static clist * read_distrib_default_value_list(newsnntp * f)
   while (1) {
     char * p;
     char * remaining;
-      
+
     line = read_line(f);
     if (line == NULL)
       goto free_list;
@@ -2141,7 +2141,7 @@ static clist * read_distrib_default_value_list(newsnntp * f)
     weight = (uint32_t)strtoul(p, &remaining, 10);
     p = remaining;
     parse_space(&p);
-      
+
     p = cut_token(line);
     if (p == NULL)
       continue;
@@ -2185,7 +2185,7 @@ static clist * read_group_description_list(newsnntp * f)
 
   while (1) {
     char * p;
-      
+
     line = read_line(f);
     if (line == NULL)
       goto free_list;
@@ -2317,30 +2317,30 @@ static clist * read_xhdr_resp_list(newsnntp * f)
 
   while (1) {
     line = read_line(f);
-    
+
     if (line == NULL)
       goto free_list;
-    
+
     if (mailstream_is_end_multiline(line))
       break;
-    
+
     article = (uint32_t) strtoul(line, &line, 10);
     if (!parse_space(&line))
       continue;
-    
+
     value = line;
-    
+
     n = xhdr_resp_item_new(article, value);
     if (n == NULL)
       goto free_list;
-    
+
     r = clist_append(xhdr_resp_list, n);
     if (r < 0) {
       xhdr_resp_item_free(n);
       goto free_list;
     }
   }
-  
+
   return xhdr_resp_list;
 
  free_list:
@@ -2367,14 +2367,14 @@ static clist * read_xover_resp_list(newsnntp * f)
   uint32_t line_count;
   clist * others;
   int r;
-  
+
   xover_resp_list = clist_new();
   if (xover_resp_list == NULL)
     goto err;
 
   while (1) {
     char * p;
-      
+
     line = read_line(f);
 
     if (line == NULL)

+ 1 - 0
deltachat-ios/libraries/deltachat-core/src/meson.build

@@ -34,6 +34,7 @@ lib_src = [
   'mrsqlite3.c',
   'mrstock.c',
   'mrtools.c',
+  'mruudecode.c',
 ]
 lib_hdr = [
   'mraheader.h',

+ 144 - 60
deltachat-ios/libraries/deltachat-core/src/mrapeerstate.c

@@ -23,6 +23,7 @@
 #include "mrmailbox_internal.h"
 #include "mrapeerstate.h"
 #include "mraheader.h"
+#include "mrhash.h"
 
 
 /*******************************************************************************
@@ -44,9 +45,14 @@ static void mrapeerstate_empty(mrapeerstate_t* ths)
 	free(ths->m_addr);
 	ths->m_addr = NULL;
 
-	free(ths->m_fingerprint);
-	ths->m_fingerprint = NULL;
-	ths->m_verified    = 0;
+	free(ths->m_public_key_fingerprint);
+	ths->m_public_key_fingerprint = NULL;
+
+	free(ths->m_gossip_key_fingerprint);
+	ths->m_gossip_key_fingerprint = NULL;
+
+	ths->m_public_key_verified = MRV_NOT_VERIFIED;
+	ths->m_gossip_key_verified = MRV_NOT_VERIFIED;
 
 	if( ths->m_public_key ) {
 		mrkey_unref(ths->m_public_key);
@@ -66,7 +72,7 @@ static void mrapeerstate_empty(mrapeerstate_t* ths)
 
 static void mrapeerstate_set_from_stmt__(mrapeerstate_t* peerstate, sqlite3_stmt* stmt)
 {
-	#define PEERSTATE_FIELDS "addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, fingerprint, verified"
+	#define PEERSTATE_FIELDS "addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, public_key_verified, gossip_key_verified"
 	peerstate->m_addr                = safe_strdup((char*)sqlite3_column_text  (stmt, 0));
 	peerstate->m_last_seen           =                    sqlite3_column_int64 (stmt, 1);
 	peerstate->m_last_seen_autocrypt =                    sqlite3_column_int64 (stmt, 2);
@@ -74,8 +80,10 @@ static void mrapeerstate_set_from_stmt__(mrapeerstate_t* peerstate, sqlite3_stmt
 	#define PUBLIC_KEY_COL                                                      4
 	peerstate->m_gossip_timestamp    =                    sqlite3_column_int   (stmt, 5);
 	#define GOSSIP_KEY_COL                                                      6
-	peerstate->m_fingerprint         = safe_strdup((char*)sqlite3_column_text  (stmt, 7));
-	peerstate->m_verified            =                    sqlite3_column_int   (stmt, 8);
+	peerstate->m_public_key_fingerprint = safe_strdup((char*)sqlite3_column_text  (stmt, 7));
+	peerstate->m_gossip_key_fingerprint = safe_strdup((char*)sqlite3_column_text  (stmt, 8));
+	peerstate->m_public_key_verified =                    sqlite3_column_int   (stmt, 9);
+	peerstate->m_gossip_key_verified =                    sqlite3_column_int   (stmt,10);
 
 	if( sqlite3_column_type(stmt, PUBLIC_KEY_COL)!=SQLITE_NULL ) {
 		peerstate->m_public_key = mrkey_new();
@@ -131,8 +139,12 @@ int mrapeerstate_load_by_fingerprint__(mrapeerstate_t* peerstate, mrsqlite3_t* s
 	stmt = mrsqlite3_predefine__(sql, SELECT_fields_FROM_acpeerstates_WHERE_fingerprint,
 		"SELECT " PEERSTATE_FIELDS
 		 " FROM acpeerstates "
-		 " WHERE fingerprint=? COLLATE NOCASE;");
+		 " WHERE public_key_fingerprint=? COLLATE NOCASE "
+		 "    OR gossip_key_fingerprint=? COLLATE NOCASE "
+		 " ORDER BY public_key_fingerprint=? DESC;"); // if for, any reasons, different peers have the same key, prefer the peer with the correct public key. should not happen, however.
 	sqlite3_bind_text(stmt, 1, fingerprint, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 2, fingerprint, -1, SQLITE_STATIC);
+	sqlite3_bind_text(stmt, 3, fingerprint, -1, SQLITE_STATIC);
 	if( sqlite3_step(stmt) != SQLITE_ROW ) {
 		goto cleanup;
 	}
@@ -165,7 +177,7 @@ int mrapeerstate_save_to_db__(const mrapeerstate_t* ths, mrsqlite3_t* sql, int c
 		stmt = mrsqlite3_predefine__(sql, UPDATE_acpeerstates_SET_lcpp_WHERE_a,
 			"UPDATE acpeerstates "
 			"   SET last_seen=?, last_seen_autocrypt=?, prefer_encrypted=?, "
-			"       public_key=?, gossip_timestamp=?, gossip_key=?, fingerprint=?, verified=? "
+			"       public_key=?, gossip_timestamp=?, gossip_key=?, public_key_fingerprint=?, gossip_key_fingerprint=?, public_key_verified=?, gossip_key_verified=? "
 			" WHERE addr=?;");
 		sqlite3_bind_int64(stmt, 1, ths->m_last_seen);
 		sqlite3_bind_int64(stmt, 2, ths->m_last_seen_autocrypt);
@@ -173,9 +185,11 @@ int mrapeerstate_save_to_db__(const mrapeerstate_t* ths, mrsqlite3_t* sql, int c
 		sqlite3_bind_blob (stmt, 4, ths->m_public_key? ths->m_public_key->m_binary : NULL/*results in sqlite3_bind_null()*/, ths->m_public_key? ths->m_public_key->m_bytes : 0, SQLITE_STATIC);
 		sqlite3_bind_int64(stmt, 5, ths->m_gossip_timestamp);
 		sqlite3_bind_blob (stmt, 6, ths->m_gossip_key? ths->m_gossip_key->m_binary : NULL/*results in sqlite3_bind_null()*/, ths->m_gossip_key? ths->m_gossip_key->m_bytes : 0, SQLITE_STATIC);
-		sqlite3_bind_text (stmt, 7, ths->m_fingerprint, -1, SQLITE_STATIC);
-		sqlite3_bind_int  (stmt, 8, ths->m_verified);
-		sqlite3_bind_text (stmt, 9, ths->m_addr, -1, SQLITE_STATIC);
+		sqlite3_bind_text (stmt, 7, ths->m_public_key_fingerprint, -1, SQLITE_STATIC);
+		sqlite3_bind_text (stmt, 8, ths->m_gossip_key_fingerprint, -1, SQLITE_STATIC);
+		sqlite3_bind_int  (stmt, 9, ths->m_public_key_verified);
+		sqlite3_bind_int  (stmt,10, ths->m_gossip_key_verified);
+		sqlite3_bind_text (stmt,11, ths->m_addr, -1, SQLITE_STATIC);
 		if( sqlite3_step(stmt) != SQLITE_DONE ) {
 			goto cleanup;
 		}
@@ -244,7 +258,7 @@ void mrapeerstate_unref(mrapeerstate_t* ths)
  *     `Autocrypt-Gossip:` is _not_ included in the returned string. If there
  *     is not key for the peer that can be gossiped, NULL is returned.
  */
-char* mrapeerstate_render_gossip_header(const mrapeerstate_t* peerstate)
+char* mrapeerstate_render_gossip_header(const mrapeerstate_t* peerstate, int min_verified)
 {
 	char*        ret = NULL;
 	mraheader_t* autocryptheader = mraheader_new();
@@ -255,7 +269,7 @@ char* mrapeerstate_render_gossip_header(const mrapeerstate_t* peerstate)
 
 	autocryptheader->m_prefer_encrypt = MRA_PE_NOPREFERENCE; /* the spec says, we SHOULD NOT gossip this flag */
 	autocryptheader->m_addr           = safe_strdup(peerstate->m_addr);
-	autocryptheader->m_public_key     = mrkey_ref(mrapeerstate_peek_key(peerstate)); /* may be NULL */
+	autocryptheader->m_public_key     = mrkey_ref(mrapeerstate_peek_key(peerstate, min_verified)); /* may be NULL */
 
 	ret = mraheader_render(autocryptheader);
 
@@ -266,7 +280,7 @@ cleanup:
 
 
 /**
- * Return either m_public_key or m_gossip_key if m_public_key is null.
+ * Return either m_public_key or m_gossip_key if m_public_key is null or not verified.
  * The function does not check if the keys are valid but the caller can assume
  * the returned key has data.
  *
@@ -276,31 +290,41 @@ cleanup:
  * @memberof mrapeerstate_t
  *
  * @param peerstate The peerstate object.
+ * @param min_verified The minimal verification criterion the key should match.
+ *     Typically either MRV_NOT_VERIFIED (0) if there is no need for the key being verified
+ *     or MRV_BIDIRECTIONAL (2) for bidirectional verification requirement.
  *
  * @return m_public_key or m_gossip_key, NULL if nothing is available.
  *     the returned pointer MUST NOT be unref()'d.
  */
-mrkey_t* mrapeerstate_peek_key(const mrapeerstate_t* peerstate)
+mrkey_t* mrapeerstate_peek_key(const mrapeerstate_t* peerstate, int min_verified)
 {
-	if( peerstate == NULL ) {
-		return NULL; /* error */
+	if(  peerstate == NULL
+	 || (peerstate->m_public_key && (peerstate->m_public_key->m_binary==NULL || peerstate->m_public_key->m_bytes<=0))
+	 || (peerstate->m_gossip_key && (peerstate->m_gossip_key->m_binary==NULL || peerstate->m_gossip_key->m_bytes<=0)) ) {
+		return NULL;
 	}
 
-	if( peerstate->m_public_key ) {
-		if( peerstate->m_public_key->m_binary==NULL || peerstate->m_public_key->m_bytes<=0 ) {
-			return NULL; /* error */
-		}
-		return peerstate->m_public_key; /* use this key */
+	if( min_verified == MRV_BIDIRECTIONAL
+	 && peerstate->m_public_key_verified == MRV_BIDIRECTIONAL
+	 && peerstate->m_gossip_key_verified == MRV_BIDIRECTIONAL  )
+	{
+		// have two verified keys, use the key that is newer
+		return peerstate->m_gossip_timestamp > peerstate->m_last_seen_autocrypt?
+			peerstate->m_gossip_key : peerstate->m_public_key;
 	}
 
-	if( peerstate->m_gossip_key ) {
-		if( peerstate->m_gossip_key->m_binary==NULL || peerstate->m_gossip_key->m_bytes<=0 ) {
-			return NULL; /* error */
-		}
-		return peerstate->m_gossip_key; /* use this key */
+	if( peerstate->m_public_key && peerstate->m_public_key_verified>=min_verified )
+	{
+		return peerstate->m_public_key;
+	}
+
+	if( peerstate->m_gossip_key && peerstate->m_gossip_key_verified>=min_verified )
+	{
+		return peerstate->m_gossip_key;
 	}
 
-	return NULL; /* no key available */
+	return NULL; // no key with the desired verification available
 }
 
 
@@ -436,8 +460,7 @@ void mrapeerstate_apply_gossip(mrapeerstate_t* peerstate, const mraheader_t* gos
 
 
 /*
- * Recalculate the fingerprint for the key returned by mrapeerstate_peek_key()
- * (public_key, if set, gossip_key otherwise).
+ * Recalculate the fingerprints for the keys.
  *
  * If the fingerprint has changed, the verified-state is reset.
  *
@@ -448,39 +471,65 @@ void mrapeerstate_apply_gossip(mrapeerstate_t* peerstate, const mraheader_t* gos
 int mrapeerstate_recalc_fingerprint(mrapeerstate_t* peerstate)
 {
 	int            success = 0;
-	const mrkey_t* key = NULL;
-	char*          old_fingerprint = NULL;
+	char*          old_public_fingerprint = NULL, *old_gossip_fingerprint = NULL;
+	int            has_old_verified_key = (mrapeerstate_peek_key(peerstate, MRV_BIDIRECTIONAL)!=NULL);
 
 	if( peerstate == NULL ) {
 		goto cleanup;
 	}
 
-	if( (key = mrapeerstate_peek_key(peerstate)) == NULL ) {
-		goto cleanup;
-	}
+	if( peerstate->m_public_key )
+	{
+		old_public_fingerprint = peerstate->m_public_key_fingerprint;
+		peerstate->m_public_key_fingerprint = mrkey_get_fingerprint(peerstate->m_public_key); /* returns the empty string for errors, however, this should be saved as well as it represents an erroneous key */
+
+		if( old_public_fingerprint == NULL
+		 || old_public_fingerprint[0] == 0
+		 || peerstate->m_public_key_fingerprint == NULL
+		 || peerstate->m_public_key_fingerprint[0] == 0
+		 || strcasecmp(old_public_fingerprint, peerstate->m_public_key_fingerprint) != 0 )
+		{
+			peerstate->m_to_save  |= MRA_SAVE_ALL;
 
-	old_fingerprint = peerstate->m_fingerprint;
+			peerstate->m_public_key_verified = MRV_NOT_VERIFIED;
 
-	peerstate->m_fingerprint = mrkey_get_fingerprint(key); /* returns the empty string for errors, however, this should be saved as well as it represents an erroneous key */
+			if( old_public_fingerprint && old_public_fingerprint[0] ) { // no degrade event when we recveive just the initial fingerprint
+				peerstate->m_degrade_event |= MRA_DE_FINGERPRINT_CHANGED;
+			}
+		}
+	}
 
-	if( old_fingerprint == NULL
-	 || old_fingerprint[0] == 0
-	 || peerstate->m_fingerprint == NULL
-	 || peerstate->m_fingerprint[0] == 0
-	 || strcasecmp(old_fingerprint, peerstate->m_fingerprint) != 0 )
+	if( peerstate->m_gossip_key )
 	{
-		peerstate->m_to_save  |= MRA_SAVE_ALL;
-		peerstate->m_verified = 0;
+		old_gossip_fingerprint = peerstate->m_gossip_key_fingerprint;
+		peerstate->m_gossip_key_fingerprint = mrkey_get_fingerprint(peerstate->m_gossip_key); /* returns the empty string for errors, however, this should be saved as well as it represents an erroneous key */
+
+		if( old_gossip_fingerprint == NULL
+		 || old_gossip_fingerprint[0] == 0
+		 || peerstate->m_gossip_key_fingerprint == NULL
+		 || peerstate->m_gossip_key_fingerprint[0] == 0
+		 || strcasecmp(old_gossip_fingerprint, peerstate->m_gossip_key_fingerprint) != 0 )
+		{
+			peerstate->m_to_save  |= MRA_SAVE_ALL;
+
+			peerstate->m_gossip_key_verified = MRV_NOT_VERIFIED;
 
-		if( old_fingerprint && old_fingerprint[0] ) { // no degrade event when we recveive just the initial fingerprint
-			peerstate->m_degrade_event |= MRA_DE_FINGERPRINT_CHANGED;
+			if( old_gossip_fingerprint && old_gossip_fingerprint[0] ) { // no degrade event when we recveive just the initial fingerprint
+				peerstate->m_degrade_event |= MRA_DE_FINGERPRINT_CHANGED;
+			}
 		}
 	}
 
+	if( has_old_verified_key
+	 && (mrapeerstate_peek_key(peerstate, MRV_BIDIRECTIONAL)==NULL) ) {
+		peerstate->m_degrade_event |= MRA_DE_VERIFICATION_LOST;
+	}
+
 	success = 1;
 
 cleanup:
-	free(old_fingerprint);
+	free(old_public_fingerprint);
+	free(old_gossip_fingerprint);
 	return success;
 }
 
@@ -495,7 +544,9 @@ cleanup:
  * @memberof mrapeerstate_t
  *
  * @param peerstate The peerstate object.
+ * @param which_key Which key should be marked as being verified? MRA_GOSSIP_KEY (1) or MRA_PUBLIC_KEY (2)
  * @param fingerprint Fingerprint expected in the object
+ * @param verified MRV_SIMPLE (1): we verified the contact, MRV_BIDIRECTIONAL (2): contact verfied in both directions
  *
  * @return 1=the given fingerprint is equal to the peer's fingerprint and
  *     the verified-state is set; you should call mrapeerstate_save_to_db__()
@@ -503,27 +554,60 @@ cleanup:
  *     0=the given fingerprint is not eqial to the peer's fingerprint,
  *     verified-state not changed.
  */
-int mrapeerstate_set_verified(mrapeerstate_t* peerstate, const char* fingerprint)
+int mrapeerstate_set_verified(mrapeerstate_t* peerstate, int which_key, const char* fingerprint, int verified)
 {
-	int verified = 0;
+	int success = 0;
 
-	if( peerstate == NULL || fingerprint == NULL ) {
+	if( peerstate == NULL
+	 || (which_key!=MRA_GOSSIP_KEY && which_key!=MRA_PUBLIC_KEY)
+	 || (verified!=MRV_SIMPLE && verified!=MRV_BIDIRECTIONAL) ) {
 		goto cleanup;
 	}
 
-	if( peerstate->m_fingerprint ==  NULL
-	 || peerstate->m_fingerprint[0] == 0
-	 || fingerprint[0] == 0
-	 || strcasecmp(peerstate->m_fingerprint, fingerprint) != 0 )
+	if( which_key == MRA_PUBLIC_KEY
+	 && peerstate->m_public_key_fingerprint != NULL
+	 && peerstate->m_public_key_fingerprint[0] != 0
+	 && fingerprint[0] != 0
+	 && strcasecmp(peerstate->m_public_key_fingerprint, fingerprint) == 0 )
 	{
-		goto cleanup;
+		peerstate->m_to_save            |= MRA_SAVE_ALL;
+		peerstate->m_public_key_verified = verified;
+		success                          = 1;
 	}
 
-	peerstate->m_to_save        |= MRA_SAVE_ALL;
-	peerstate->m_prefer_encrypt =  MRA_PE_MUTUAL;
-	peerstate->m_verified       = 1;
-	verified                    = 1;
+	if( which_key == MRA_GOSSIP_KEY
+	 && peerstate->m_gossip_key_fingerprint != NULL
+	 && peerstate->m_gossip_key_fingerprint[0] != 0
+	 && fingerprint[0] != 0
+	 && strcasecmp(peerstate->m_gossip_key_fingerprint, fingerprint) == 0 )
+	{
+		peerstate->m_to_save            |= MRA_SAVE_ALL;
+		peerstate->m_gossip_key_verified = verified;
+		success                          = 1;
+	}
 
 cleanup:
-	return verified;
+	return success;
+}
+
+
+int mrapeerstate_has_verified_key(const mrapeerstate_t* peerstate, const mrhash_t* fingerprints)
+{
+	if( peerstate == NULL || fingerprints == NULL ) {
+		return 0;
+	}
+
+	if( peerstate->m_public_key_verified >= MRV_BIDIRECTIONAL
+	 && peerstate->m_public_key_fingerprint
+	 && mrhash_find_str(fingerprints, peerstate->m_public_key_fingerprint) ) {
+		return 1;
+	}
+
+	if( peerstate->m_gossip_key_verified >= MRV_BIDIRECTIONAL
+	 && peerstate->m_gossip_key_fingerprint
+	 && mrhash_find_str(fingerprints, peerstate->m_gossip_key_fingerprint) ) {
+		return 1;
+	}
+
+	return 0;
 }

+ 18 - 8
deltachat-ios/libraries/deltachat-core/src/mrapeerstate.h

@@ -50,14 +50,20 @@ typedef struct mrapeerstate_t
 	time_t         m_last_seen;  /* may be 0 if the peer was created by gossipping */
 
 	time_t         m_last_seen_autocrypt;
-	mrkey_t*       m_public_key; /* may be NULL, however, in the database, either public_key or gossip_key is set */
 	int            m_prefer_encrypt;
 
-	time_t         m_gossip_timestamp;
-	mrkey_t*       m_gossip_key; /* may be NULL */
+	#define        MRV_NOT_VERIFIED  0
+	#define        MRV_SIMPLE        1
+	#define        MRV_BIDIRECTIONAL 2
 
-	char*          m_fingerprint; /* fingerprint belonging to public_key (if set) or m_gossip_key (otherwise), may be NULL */
-	int            m_verified;    // fingerprint verified?
+	mrkey_t*       m_public_key; /* may be NULL, however, in the database, either public_key or gossip_key is set */
+	char*          m_public_key_fingerprint;
+	int            m_public_key_verified;
+
+	mrkey_t*       m_gossip_key; /* may be NULL */
+	time_t         m_gossip_timestamp;
+	char*          m_gossip_key_fingerprint;
+	int            m_gossip_key_verified;
 
 	#define        MRA_SAVE_TIMESTAMPS 0x01
 	#define        MRA_SAVE_ALL        0x02
@@ -65,6 +71,7 @@ typedef struct mrapeerstate_t
 
 	#define        MRA_DE_ENCRYPTION_PAUSED   0x01 // recoverable by an incoming encrypted mail
 	#define        MRA_DE_FINGERPRINT_CHANGED 0x02 // recoverable by a new verify
+	#define        MRA_DE_VERIFICATION_LOST   0x04 // recoverable by a new verify
 	int            m_degrade_event;
 
 } mrapeerstate_t;
@@ -81,18 +88,21 @@ int             mrapeerstate_degrade_encryption   (mrapeerstate_t*, time_t messa
 void            mrapeerstate_apply_header         (mrapeerstate_t*, const mraheader_t*, time_t message_time);
 void            mrapeerstate_apply_gossip         (mrapeerstate_t*, const mraheader_t*, time_t message_time);
 
-char*           mrapeerstate_render_gossip_header (const mrapeerstate_t*);
+char*           mrapeerstate_render_gossip_header (const mrapeerstate_t*, int min_verified);
 
-mrkey_t*        mrapeerstate_peek_key             (const mrapeerstate_t*);
+mrkey_t*        mrapeerstate_peek_key             (const mrapeerstate_t*, int min_verified);
 
 int             mrapeerstate_recalc_fingerprint   (mrapeerstate_t*);
 
-int             mrapeerstate_set_verified         (mrapeerstate_t*, const char* fingerprint);
+#define         MRA_GOSSIP_KEY 0
+#define         MRA_PUBLIC_KEY 1
+int             mrapeerstate_set_verified         (mrapeerstate_t*, int which_key, const char* fingerprint, int verfied);
 
 int             mrapeerstate_load_by_addr__       (mrapeerstate_t*, mrsqlite3_t*, const char* addr);
 int             mrapeerstate_load_by_fingerprint__(mrapeerstate_t*, mrsqlite3_t*, const char* fingerprint);
 int             mrapeerstate_save_to_db__         (const mrapeerstate_t*, mrsqlite3_t*, int create);
 
+int             mrapeerstate_has_verified_key     (const mrapeerstate_t*, const mrhash_t* fingerprints);
 
 #ifdef __cplusplus
 } /* /extern "C" */

+ 2 - 2
deltachat-ios/libraries/deltachat-core/src/mrarray.c

@@ -63,7 +63,7 @@ mrarray_t* mrarray_new(mrmailbox_t* mailbox, size_t initsize)
  *
  * @memberof mrarray_t
  *
- * @param array The array object to free, created eg. by mrmailbox_get_chatlist(), mrmailbox_get_known_contacts() and so on.
+ * @param array The array object to free, created eg. by mrmailbox_get_chatlist(), mrmailbox_get_contacts() and so on.
  *
  * @return None.
  *
@@ -436,7 +436,7 @@ char* mrarray_get_string(const mrarray_t* array, const char* sep)
 	char* ret = NULL;
 
 	if( array == NULL || array->m_magic != MR_ARRAY_MAGIC || sep==NULL ) {
-		return NULL;
+		return safe_strdup("");
 	}
 
 	INT_ARR_TO_STR(array->m_array, array->m_count);

+ 7 - 2
deltachat-ios/libraries/deltachat-core/src/mrchat-private.h

@@ -51,8 +51,13 @@ struct _mrchat
 };
 
 
-int             mrchat_load_from_db__       (mrchat_t*, uint32_t id);
-int             mrchat_update_param__       (mrchat_t*);
+int             mrchat_load_from_db__             (mrchat_t*, uint32_t id);
+int             mrchat_update_param__             (mrchat_t*);
+int             mrchat_are_all_members_verified__ (mrchat_t*);
+
+
+#define         MR_CHAT_TYPE_IS_MULTI(a)   ((a)==MR_CHAT_TYPE_GROUP || (a)==MR_CHAT_TYPE_VERIFIED_GROUP)
+#define         MR_CHAT_TYPE_CAN_SEND(a)   ((a)==MR_CHAT_TYPE_SINGLE || (a)==MR_CHAT_TYPE_GROUP || (a)==MR_CHAT_TYPE_VERIFIED_GROUP)
 
 
 #define         MR_CHAT_PREFIX              "Chat:"      /* you MUST NOT modify this or the following strings */

+ 45 - 28
deltachat-ios/libraries/deltachat-core/src/mrchat.c

@@ -149,7 +149,7 @@ uint32_t mrchat_get_id(mrchat_t* chat)
  *
  * Currently, there are two chat types:
  *
- * - MR_CHAT_TYPE_NORMAL (100) - a normal chat is a chat with a single contact,
+ * - MR_CHAT_TYPE_SINGLE (100) - a normal chat is a chat with a single contact,
  *   chats_contacts contains one record for the user.  MR_CONTACT_ID_SELF
  *   (see mrcontact_t::m_id) is added _only_ for a self talk; in addition to
  *   this, for self talks also the flag MRP_SELFTALK is set.
@@ -157,6 +157,9 @@ uint32_t mrchat_get_id(mrchat_t* chat)
  * - MR_CHAT_TYPE_GROUP  (120) - a group chat, chats_contacts conain all group
  *   members, incl. MR_CONTACT_ID_SELF
  *
+ * - MR_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 mrchat_t
  *
  * @param chat The chat object.
@@ -166,7 +169,7 @@ uint32_t mrchat_get_id(mrchat_t* chat)
 int mrchat_get_type(mrchat_t* chat)
 {
 	if( chat == NULL || chat->m_magic != MR_CHAT_MAGIC ) {
-		return MR_CHAT_TYPE_NORMAL;
+		return MR_CHAT_TYPE_UNDEFINED;
 	}
 	return chat->m_type;
 }
@@ -219,11 +222,11 @@ char* mrchat_get_subtitle(mrchat_t* chat)
 		return safe_strdup("Err");
 	}
 
-	if( chat->m_type == MR_CHAT_TYPE_NORMAL && mrparam_exists(chat->m_param, MRP_SELFTALK) )
+	if( chat->m_type == MR_CHAT_TYPE_SINGLE && mrparam_exists(chat->m_param, MRP_SELFTALK) )
 	{
 		ret = mrstock_str(MR_STR_SELFTALK_SUBTITLE);
 	}
-	else if( chat->m_type == MR_CHAT_TYPE_NORMAL )
+	else if( chat->m_type == MR_CHAT_TYPE_SINGLE )
 	{
 		int r;
 		mrsqlite3_lock(chat->m_mailbox->m_sql);
@@ -241,7 +244,7 @@ char* mrchat_get_subtitle(mrchat_t* chat)
 
 		mrsqlite3_unlock(chat->m_mailbox->m_sql);
 	}
-	else if( chat->m_type == MR_CHAT_TYPE_GROUP )
+	else if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 	{
 		int cnt = 0;
 		if( chat->m_id == MR_CHAT_ID_DEADDROP )
@@ -378,12 +381,29 @@ int mrchat_is_unpromoted(mrchat_t* chat)
 }
 
 
+/**
+ * Check if a chat is verified.  Verified chats contain only verified members
+ * and encryption is alwasy enabled.  Verified chats are created using
+ * mrmailbox_create_group_chat() by setting the 'verified' parameter to true.
+ *
+ * @memberof mrchat_t
+ *
+ * @param chat The chat object.
+ *
+ * @return 1=chat verified, 0=chat is not verified
+ */
 int mrchat_is_verified(mrchat_t* chat)
 {
-	// if you change the algorithm here, you may also want to adapt mrcontact_is_verfied()
+	if( chat == NULL || chat->m_magic != MR_CHAT_MAGIC ) {
+		return 0;
+	}
+	return (chat->m_type==MR_CHAT_TYPE_VERIFIED_GROUP);
+}
+
 
+int mrchat_are_all_members_verified__(mrchat_t* chat)
+{
 	int           chat_verified = 0;
-	int           locked = 0;
 	sqlite3_stmt* stmt;
 
 	if( chat == NULL || chat->m_magic != MR_CHAT_MAGIC ) {
@@ -394,32 +414,29 @@ int mrchat_is_verified(mrchat_t* chat)
 		goto cleanup; // deaddrop & co. are never verified
 	}
 
-	mrsqlite3_lock(chat->m_mailbox->m_sql);
-	locked = 1;
-
-		stmt = mrsqlite3_predefine__(chat->m_mailbox->m_sql, SELECT_verified_FROM_chats_contacts_WHERE_chat_id,
-			"SELECT c.id, ps.verified, ps.prefer_encrypted "
-			" FROM chats_contacts cc"
-			" LEFT JOIN contacts c ON c.id=cc.contact_id"
-			" LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
-			" WHERE cc.chat_id=?;");
-		sqlite3_bind_int(stmt, 1, chat->m_id);
-		while( sqlite3_step(stmt) == SQLITE_ROW )
+	stmt = mrsqlite3_predefine__(chat->m_mailbox->m_sql, SELECT_verified_FROM_chats_contacts_WHERE_chat_id,
+		"SELECT c.id, ps.public_key_verified, ps.gossip_key_verified "
+		" FROM chats_contacts cc"
+		" LEFT JOIN contacts c ON c.id=cc.contact_id"
+		" LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
+		" WHERE cc.chat_id=?;");
+	sqlite3_bind_int(stmt, 1, chat->m_id);
+	while( sqlite3_step(stmt) == SQLITE_ROW )
+	{
+		uint32_t contact_id          = sqlite3_column_int(stmt, 0);
+		int      public_key_verified = sqlite3_column_int(stmt, 1);
+		int      gossip_key_verified = sqlite3_column_int(stmt, 2);
+		if( contact_id != MR_CONTACT_ID_SELF
+		 && public_key_verified != MRV_BIDIRECTIONAL
+		 && gossip_key_verified != MRV_BIDIRECTIONAL )
 		{
-			uint32_t contact_id       = sqlite3_column_int(stmt, 0);
-			int      contact_verified = sqlite3_column_int(stmt, 1);
-			int      prefer_encrypt   = sqlite3_column_int(stmt, 2);
-			if( contact_id != MR_CONTACT_ID_SELF // see mrcontact_is_verified() for condition's explanations
-			 && (contact_verified==0 || prefer_encrypt!=MRA_PE_MUTUAL) )
-			{
-				goto cleanup; // a single unverified contact results in an unverified chat
-			}
+			goto cleanup; // a single unverified contact results in an unverified chat
 		}
+	}
 
-		chat_verified = 1;
+	chat_verified = 1;
 
 cleanup:
-	if( locked ) { mrsqlite3_unlock(chat->m_mailbox->m_sql); }
 	return chat_verified;
 }
 

+ 6 - 3
deltachat-ios/libraries/deltachat-core/src/mrchat.h

@@ -36,9 +36,12 @@ typedef struct _mrmailbox mrmailbox_t;
 #define         MR_CHAT_ID_STARRED          5 /* virtual chat showing all messages flagged with msgs.starred=2 */
 #define         MR_CHAT_ID_ARCHIVED_LINK    6 /* only an indicator in a chatlist */
 #define         MR_CHAT_ID_LAST_SPECIAL     9 /* larger chat IDs are "real" chats, their messages are "real" messages. */
-#define         MR_CHAT_TYPE_UNDEFINED      0
-#define         MR_CHAT_TYPE_NORMAL       100
-#define         MR_CHAT_TYPE_GROUP        120
+
+
+#define         MR_CHAT_TYPE_UNDEFINED        0
+#define         MR_CHAT_TYPE_SINGLE         100
+#define         MR_CHAT_TYPE_GROUP          120
+#define         MR_CHAT_TYPE_VERIFIED_GROUP 130
 
 
 /**

+ 1 - 1
deltachat-ios/libraries/deltachat-core/src/mrchatlist-private.h

@@ -39,7 +39,7 @@ struct _mrchatlist
 };
 
 
-int             mrchatlist_load_from_db__   (mrchatlist_t*, int listflags, const char* query);
+int             mrchatlist_load_from_db__   (mrchatlist_t*, int listflags, const char* query, uint32_t query_contact_id);
 
 
 #ifdef __cplusplus

+ 21 - 4
deltachat-ios/libraries/deltachat-core/src/mrchatlist.c

@@ -231,7 +231,7 @@ mrlot_t* mrchatlist_get_summary(mrchatlist_t* chatlist, size_t index, mrchat_t*
 			lastmsg = mrmsg_new();
 			mrmsg_load_from_db__(lastmsg, chatlist->m_mailbox, lastmsg_id);
 
-			if( lastmsg->m_from_id != MR_CONTACT_ID_SELF  &&  chat->m_type == MR_CHAT_TYPE_GROUP )
+			if( lastmsg->m_from_id != MR_CONTACT_ID_SELF  &&  MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 			{
 				lastcontact = mrcontact_new(chatlist->m_mailbox);
 				mrcontact_load_from_db__(lastcontact, chatlist->m_mailbox->m_sql, lastmsg->m_from_id);
@@ -304,8 +304,10 @@ mrmailbox_t* mrchatlist_get_mailbox(mrchatlist_t* chatlist)
  *
  * @private @memberof mrchatlist_t
  */
-int mrchatlist_load_from_db__(mrchatlist_t* ths, int listflags, const char* query__)
+int mrchatlist_load_from_db__(mrchatlist_t* ths, int listflags, const char* query__, uint32_t query_contact_id)
 {
+	clock_t       start = clock();
+
 	int           success = 0;
 	int           add_archived_link_item = 0;
 	sqlite3_stmt* stmt = NULL;
@@ -319,12 +321,25 @@ int mrchatlist_load_from_db__(mrchatlist_t* ths, int listflags, const char* quer
 
 	/* select example with left join and minimum: http://stackoverflow.com/questions/7588142/mysql-left-join-min */
 	#define QUR1 "SELECT c.id, m.id FROM chats c " \
-	                " LEFT JOIN msgs m ON (c.id=m.chat_id AND m.timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND hidden=0)) " \
+	                " LEFT JOIN msgs m ON (c.id=m.chat_id AND m.hidden=0 AND m.timestamp=(SELECT MAX(timestamp) FROM msgs WHERE chat_id=c.id AND hidden=0)) " /* not: `m.hidden` which would refer the outer select and takes lot of time*/ \
 	                " WHERE c.id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) " AND c.blocked=0"
 	#define QUR2    " GROUP BY c.id " /* GROUP BY is needed as there may be several messages with the same timestamp */ \
 	                " ORDER BY MAX(c.draft_timestamp, IFNULL(m.timestamp,0)) DESC,m.id DESC;" /* the list starts with the newest chats */
 
-	if( listflags & MR_GCL_ARCHIVED_ONLY )
+	// nb: the query currently shows messages from blocked contacts in groups.
+	// however, for normal-groups, this is okay as the message is also returned by mrmailbox_get_chat_msgs()
+	// (otherwise it would be hard to follow conversations, wa and tg do the same)
+	// for the deaddrop, however, they should really be hidden, however, _currently_ the deaddrop is not
+	// shown at all permanent in the chatlist.
+
+	if( query_contact_id )
+	{
+		// show chats shared with a given contact
+		stmt = mrsqlite3_predefine__(ths->m_mailbox->m_sql, SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_contact_id,
+			QUR1 " AND c.id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?) " QUR2);
+		sqlite3_bind_int(stmt, 1, query_contact_id);
+	}
+	else if( listflags & MR_GCL_ARCHIVED_ONLY )
 	{
 		/* show archived chats */
 		stmt = mrsqlite3_predefine__(ths->m_mailbox->m_sql, SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_archived,
@@ -376,6 +391,8 @@ int mrchatlist_load_from_db__(mrchatlist_t* ths, int listflags, const char* quer
 	success = 1;
 
 cleanup:
+	mrmailbox_log_info(ths->m_mailbox, 0, "Chatlist for search \"%s\" created in %.3f ms.", query__?query__:"", (double)(clock()-start)*1000.0/CLOCKS_PER_SEC);
+
 	free(query);
 	free(strLikeCmd);
 	return success;

+ 2 - 0
deltachat-ios/libraries/deltachat-core/src/mrcontact-private.h

@@ -28,6 +28,7 @@ extern "C" {
 
 
 typedef struct mrsqlite3_t mrsqlite3_t;
+typedef struct mrapeerstate_t mrapeerstate_t;
 
 
 /** the structure behind mrcontact_t */
@@ -78,6 +79,7 @@ struct _mrcontact
 #define MR_ORIGIN_MIN_START_NEW_NCHAT (0x7FFFFFFF)                  /* contacts with at least this origin value start a new "normal" chat, defaults to off */
 
 int          mrcontact_load_from_db__         (mrcontact_t*, mrsqlite3_t*, uint32_t contact_id);
+int          mrcontact_is_verified__          (const mrcontact_t*, const mrapeerstate_t*);
 void         mr_normalize_name                (char* full_name);
 char*        mr_normalize_addr                (const char* email_addr);
 char*        mr_get_first_name                (const char* full_name);

+ 67 - 24
deltachat-ios/libraries/deltachat-core/src/mrcontact.c

@@ -119,7 +119,7 @@ void mrcontact_empty(mrcontact_t* contact)
  *
  * @return the ID of the contact, 0 on errors.
  */
-uint32_t mrcontact_get_id(mrcontact_t* contact)
+uint32_t mrcontact_get_id(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return 0;
@@ -137,7 +137,7 @@ uint32_t mrcontact_get_id(mrcontact_t* contact)
  *
  * @return String with the email address, must be free()'d. Never returns NULL.
  */
-char* mrcontact_get_addr(mrcontact_t* contact)
+char* mrcontact_get_addr(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return safe_strdup(NULL);
@@ -161,7 +161,7 @@ char* mrcontact_get_addr(mrcontact_t* contact)
  *
  * @return String with the name to display, must be free()'d. Empty string if unset, never returns NULL.
  */
-char* mrcontact_get_name(mrcontact_t* contact)
+char* mrcontact_get_name(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return safe_strdup(NULL);
@@ -176,7 +176,7 @@ char* mrcontact_get_name(mrcontact_t* contact)
  * modified by the user or, if both are unset, the email address.
  *
  * This name is typically used in lists and must not be speaded via mail (To:, CC: ...).
- * To get the name editable in a formular, use mrcontact_get_edit_name().
+ * To get the name editable in a formular, use mrcontact_get_name().
  *
  * @memberof mrcontact_t
  *
@@ -184,7 +184,7 @@ char* mrcontact_get_name(mrcontact_t* contact)
  *
  * @return String with the name to display, must be free()'d. Never returns NULL.
  */
-char* mrcontact_get_display_name(mrcontact_t* contact)
+char* mrcontact_get_display_name(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return safe_strdup(NULL);
@@ -215,7 +215,7 @@ char* mrcontact_get_display_name(mrcontact_t* contact)
  *
  * @return Summary string, must be free()'d. Never returns NULL.
  */
-char* mrcontact_get_name_n_addr(mrcontact_t* contact)
+char* mrcontact_get_name_n_addr(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return safe_strdup(NULL);
@@ -229,6 +229,31 @@ char* mrcontact_get_name_n_addr(mrcontact_t* contact)
 }
 
 
+/**
+ * Get the part of the name before the first space. In most languages, this seems to be
+ * the prename. If there is no space, the full display name is returned.
+ * If the display name is not set, the e-mail address is returned.
+ *
+ * @memberof mrcontact_t
+ *
+ * @param contact The contact object.
+ *
+ * @return String with the name to display, must be free()'d. Never returns NULL.
+ */
+char* mrcontact_get_first_name(const mrcontact_t* contact)
+{
+	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
+		return safe_strdup(NULL);
+	}
+
+	if( contact->m_name && contact->m_name[0] ) {
+		return mr_get_first_name(contact->m_name);
+	}
+
+	return safe_strdup(contact->m_addr);
+}
+
+
 /**
  * Check if a contact is blocked.
  *
@@ -240,7 +265,7 @@ char* mrcontact_get_name_n_addr(mrcontact_t* contact)
  *
  * @return 1=contact is blocked, 0=contact is not blocked.
  */
-int mrcontact_is_blocked(mrcontact_t* contact)
+int mrcontact_is_blocked(const mrcontact_t* contact)
 {
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		return 0;
@@ -249,6 +274,26 @@ int mrcontact_is_blocked(mrcontact_t* contact)
 }
 
 
+int mrcontact_is_verified__(const mrcontact_t* contact, const mrapeerstate_t* peerstate)
+{
+	int             contact_verified = MRV_NOT_VERIFIED;
+
+	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
+		goto cleanup;
+	}
+
+	if( contact->m_id == MR_CONTACT_ID_SELF ) {
+		contact_verified = MRV_BIDIRECTIONAL;
+		goto cleanup; // we're always sort of secured-verified as we could verify the key on this device any time with the key on this device
+	}
+
+	contact_verified = MR_MAX(peerstate->m_public_key_verified, peerstate->m_gossip_key_verified);
+
+cleanup:
+	return contact_verified;
+}
+
+
 /**
  * Check if a contact was verified eg. by a secure-join QR code scan
  * and if the key has not changed since this verification.
@@ -259,35 +304,33 @@ int mrcontact_is_blocked(mrcontact_t* contact)
  *
  * @param contact The contact object.
  *
- * @return 1=contact is verified, 0=contact is not verified.
+ * @return MRV_NOT_VERIFIED (0): contact is not verified.
+ *    MRV_SIMPLE (1): =SELF has verified the contact but not the other way round.
+ *    MRV_BIDIRECTIONAL (2): SELF and contact have verified their fingerprints in both directions; in the UI typically checkmarks are shown.
  */
-int mrcontact_is_verified(mrcontact_t* contact)
+int mrcontact_is_verified(const mrcontact_t* contact)
 {
-	// if you change the algorithm here, you may also want to adapt mrchat_is_verfied()
-
-	int             contact_verified = 0;
-	mrapeerstate_t* peerstate        = mrapeerstate_new(contact->m_mailbox);
+	int             contact_verified = MRV_NOT_VERIFIED;
+	int             locked           = 0;
+	mrapeerstate_t* peerstate        = NULL;
 
 	if( contact == NULL || contact->m_magic != MR_CONTACT_MAGIC ) {
 		goto cleanup;
 	}
 
-	if( contact->m_id == MR_CONTACT_ID_SELF ) {
-		contact_verified = 1;
-		goto cleanup; // we're always sort of secured-verified as we could verify the key on this device any time with the key on this device
-	}
+	peerstate = mrapeerstate_new(contact->m_mailbox);
 
-	if( !mrapeerstate_load_by_addr__(peerstate, contact->m_mailbox->m_sql, contact->m_addr) ) {
-		goto cleanup;
-	}
+	mrsqlite3_lock(contact->m_mailbox->m_sql);
+	locked = 1;
 
-	if( peerstate->m_verified==0 || peerstate->m_prefer_encrypt!=MRA_PE_MUTUAL ) {
-		goto cleanup;
-	}
+		if( !mrapeerstate_load_by_addr__(peerstate, contact->m_mailbox->m_sql, contact->m_addr) ) {
+			goto cleanup;
+		}
 
-	contact_verified = 1;
+		contact_verified = mrcontact_is_verified__(contact, peerstate);
 
 cleanup:
+	if( locked ) { mrsqlite3_unlock(contact->m_mailbox->m_sql); }
 	mrapeerstate_unref(peerstate);
 	return contact_verified;
 }

+ 8 - 7
deltachat-ios/libraries/deltachat-core/src/mrcontact.h

@@ -45,13 +45,14 @@ mrcontact_t* mrcontact_new                    (mrmailbox_t*); /* the returned po
 void         mrcontact_empty                  (mrcontact_t*);
 void         mrcontact_unref                  (mrcontact_t*);
 
-uint32_t     mrcontact_get_id                 (mrcontact_t*);
-char*        mrcontact_get_addr               (mrcontact_t*);
-char*        mrcontact_get_name               (mrcontact_t*);
-char*        mrcontact_get_display_name       (mrcontact_t*);
-char*        mrcontact_get_name_n_addr        (mrcontact_t*);
-int          mrcontact_is_blocked             (mrcontact_t*);
-int          mrcontact_is_verified            (mrcontact_t*);
+uint32_t     mrcontact_get_id                 (const mrcontact_t*);
+char*        mrcontact_get_addr               (const mrcontact_t*);
+char*        mrcontact_get_name               (const mrcontact_t*);
+char*        mrcontact_get_display_name       (const mrcontact_t*);
+char*        mrcontact_get_name_n_addr        (const mrcontact_t*);
+char*        mrcontact_get_first_name         (const mrcontact_t*);
+int          mrcontact_is_blocked             (const mrcontact_t*);
+int          mrcontact_is_verified            (const mrcontact_t*);
 
 
 #ifdef __cplusplus

+ 40 - 2
deltachat-ios/libraries/deltachat-core/src/mrevent.h

@@ -167,7 +167,7 @@ extern "C" {
 /**
  * Contact(s) created, renamed, blocked or deleted.
  *
- * @param data1 0
+ * @param data1 If not 0, this is the contact_id of an added contact that should be selected.
  *
  * @param data2 0
  *
@@ -216,7 +216,45 @@ extern "C" {
 #define MR_EVENT_IMEX_FILE_WRITTEN        2052
 
 
-#define MR_EVENT_SECUREJOIN_REQUESTED     2060
+/**
+ * Progress information of a secure-join handshake from the view of the inviter
+ * (Alice, the person who shows the QR code).
+ *
+ * These events are typically sent after the joiner has scanned the QR code
+ * generated by mrmailbox_get_securejoin_qr().
+ *
+ * See also MR_EVENT_SECUREJOIN_JOINER_PROGRESS
+ *
+ * @param data1 ID of the contact that wants to join.
+ *
+ * @param data2 Progress as:
+ *     3=vg-request/vc-request, typically shown as "bob@addr joins.";
+ *     6=vg-contact-confirm/vc-contact-confirm, typically shown as "bob@addr verified."
+ *
+ * @return 0
+ */
+#define MR_EVENT_SECUREJOIN_INVITER_PROGRESS      2060
+
+
+/**
+ * Progress information of a secure-join handshake from the view of the joiner
+ * (Bob, the person who scans the QR code).
+ *
+ * The events are typically sent while mrmailbox_join_securejoin(), which
+ * may take some time, is executed.
+ *
+ * See also MR_EVENT_SECUREJOIN_INVITER_PROGRESS
+ *
+ * @param data1 ID of the contact that wants to join.
+ *
+ * @param data2 Progress as:
+ *     4=vg-auth-required/vc-auth-required, typically shown as
+ *     "alice@addr verified, introducing myself."
+ *     (Bob has verified alice and waits until Alice does the same for him)
+ *
+ * @return 0
+ */
+#define MR_EVENT_SECUREJOIN_JOINER_PROGRESS       2061
 
 
 /*******************************************************************************

+ 2 - 0
deltachat-ios/libraries/deltachat-core/src/mrhash.h

@@ -102,6 +102,8 @@ void*   mrhash_insert   (mrhash_t*, const void *pKey, int nKey, void *pData);
 void*   mrhash_find     (const mrhash_t*, const void *pKey, int nKey);
 void    mrhash_clear    (mrhash_t*);
 
+#define mrhash_find_str(H, s) mrhash_find((H), (s), strlen((s)))
+
 
 /*
  * Macros for looping over all elements of a hash table.  The idiom is

+ 23 - 8
deltachat-ios/libraries/deltachat-core/src/mrkey.c

@@ -475,12 +475,33 @@ char* mr_normalize_fingerprint(const char* in)
 }
 
 
+char* mr_binary_fingerprint_to_uc_hex(const uint8_t* fingerprint_buf, size_t fingerprint_bytes)
+{
+	char* fingerprint_hex = NULL;
+	int   i;
+
+	if( fingerprint_buf == NULL || fingerprint_bytes <= 0 ) {
+		goto cleanup;
+	}
+
+	if( (fingerprint_hex=calloc(1, fingerprint_bytes*2+1))==NULL ) {
+		goto cleanup;
+	}
+
+	for( i = 0; i < fingerprint_bytes; i++ ) {
+		snprintf(&fingerprint_hex[i*2], 3, "%02X", (int)fingerprint_buf[i]); /* 'X' instead of 'x' ensures the fingerprint is uppercase which is needed as we do not search case-insensitive, see comment in mrsqlite3.c */
+	}
+
+cleanup:
+	return fingerprint_hex;
+}
+
+
 char* mrkey_get_fingerprint(const mrkey_t* key)
 {
 	uint8_t* fingerprint_buf = NULL;
 	size_t   fingerprint_bytes = 0;
 	char*    fingerprint_hex = NULL;
-	int      i;
 
 	if( key == NULL ) {
 		goto cleanup;
@@ -490,13 +511,7 @@ char* mrkey_get_fingerprint(const mrkey_t* key)
 		goto cleanup;
 	}
 
-	if( (fingerprint_hex=calloc(1, fingerprint_bytes*2+1))==NULL ) {
-		goto cleanup;
-	}
-
-	for( i = 0; i < fingerprint_bytes; i++ ) {
-		snprintf(&fingerprint_hex[i*2], 3, "%02X", (int)fingerprint_buf[i]); /* 'X' instead of 'x' ensures the fingerprint is uppercase which is needed as we do not search case-insensitive, see comment in mrsqlite3.c */
-	}
+	fingerprint_hex = mr_binary_fingerprint_to_uc_hex(fingerprint_buf, fingerprint_bytes);
 
 cleanup:
 	free(fingerprint_buf);

+ 1 - 1
deltachat-ios/libraries/deltachat-core/src/mrkey.h

@@ -76,7 +76,7 @@ char* mrkey_get_fingerprint          (const mrkey_t*);
 char* mrkey_get_formatted_fingerprint(const mrkey_t*);
 
 void  mr_wipe_secret_mem(void* buf, size_t buf_bytes);
-
+char* mr_binary_fingerprint_to_uc_hex(const uint8_t* fingerprint_buf, size_t fingerprint_bytes);
 
 #ifdef __cplusplus
 } /* /extern "C" */

+ 2 - 2
deltachat-ios/libraries/deltachat-core/src/mrlot-private.h

@@ -41,8 +41,8 @@ struct _mrlot
 	uint32_t        m_id;              /**< The meaning of this value is defined by the creator of the object. 0 if not applicable. */
 
 	char*           m_fingerprint;     /**< used for qr code scanning only */
-	char*           m_random_public;   /**< used for qr code scanning only */
-	char*           m_random_secret;   /**< used for qr code scanning only */
+	char*           m_invitenumber;    /**< used for qr code scanning only */
+	char*           m_auth;            /**< used for qr code scanning only */
 };
 
 

+ 15 - 19
deltachat-ios/libraries/deltachat-core/src/mrlot.c

@@ -80,11 +80,11 @@ void mrlot_empty(mrlot_t* ths)
 	free(ths->m_fingerprint);
 	ths->m_fingerprint = NULL;
 
-	free(ths->m_random_public);
-	ths->m_random_public = NULL;
+	free(ths->m_invitenumber);
+	ths->m_invitenumber = NULL;
 
-	free(ths->m_random_secret);
-	ths->m_random_secret = NULL;
+	free(ths->m_auth);
+	ths->m_auth = NULL;
 
 	ths->m_timestamp = 0;
 	ths->m_state = 0;
@@ -210,32 +210,28 @@ void mrlot_fill(mrlot_t* ths, const mrmsg_t* msg, const mrchat_t* chat, const mr
 
 	if( msg->m_from_id == MR_CONTACT_ID_SELF )
 	{
-		ths->m_text1 = mrstock_str(MR_STR_SELF);
-		ths->m_text1_meaning = MR_TEXT1_SELF;
+		if( mrmsg_is_info(msg) ) {
+			ths->m_text1 = NULL;
+			ths->m_text1_meaning = 0;
+		}
+		else {
+			ths->m_text1 = mrstock_str(MR_STR_SELF);
+			ths->m_text1_meaning = MR_TEXT1_SELF;
+		}
 	}
 	else if( chat == NULL )
 	{
-		free(ths->m_text1);
 		ths->m_text1 = NULL;
 		ths->m_text1_meaning = 0;
 	}
-	else if( chat->m_type==MR_CHAT_TYPE_GROUP )
+	else if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 	{
-		if( contact==NULL ) {
-			free(ths->m_text1);
+		if( mrmsg_is_info(msg) || contact==NULL ) {
 			ths->m_text1 = NULL;
 			ths->m_text1_meaning = 0;
 		}
-		else if( contact->m_name && contact->m_name[0] ) {
-			ths->m_text1 = mr_get_first_name(contact->m_name);
-			ths->m_text1_meaning = MR_TEXT1_USERNAME;
-		}
-		else if( contact->m_addr && contact->m_addr[0] ) {
-			ths->m_text1 = safe_strdup(contact->m_addr);
-			ths->m_text1_meaning = MR_TEXT1_USERNAME;
-		}
 		else {
-			ths->m_text1 = safe_strdup("Unnamed contact");
+			ths->m_text1 = mrcontact_get_first_name(contact);
 			ths->m_text1_meaning = MR_TEXT1_USERNAME;
 		}
 	}

+ 19 - 6
deltachat-ios/libraries/deltachat-core/src/mrmailbox-private.h

@@ -32,6 +32,7 @@ typedef struct mrsmtp_t       mrsmtp_t;
 typedef struct mrsqlite3_t    mrsqlite3_t;
 typedef struct mrjob_t        mrjob_t;
 typedef struct mrmimeparser_t mrmimeparser_t;
+typedef struct mrhash_t       mrhash_t;
 
 
 /** Structure behind mrmailbox_t */
@@ -110,7 +111,7 @@ int             mrmailbox_get_fresh_msg_count__                   (mrmailbox_t*,
 uint32_t        mrmailbox_get_last_deaddrop_fresh_msg__           (mrmailbox_t*);
 void            mrmailbox_send_msg_to_smtp                        (mrmailbox_t*, mrjob_t*);
 void            mrmailbox_send_msg_to_imap                        (mrmailbox_t*, mrjob_t*);
-int             mrmailbox_add_contact_to_chat__                   (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
+int             mrmailbox_add_to_chat_contacts_table__            (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
 int             mrmailbox_is_contact_in_chat__                    (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
 int             mrmailbox_get_chat_contact_count__                (mrmailbox_t*, uint32_t chat_id);
 int             mrmailbox_is_group_explicitly_left__              (mrmailbox_t*, const char* grpid);
@@ -123,13 +124,15 @@ void            mrmailbox_update_server_uid__                     (mrmailbox_t*,
 void            mrmailbox_update_msg_chat_id__                    (mrmailbox_t*, uint32_t msg_id, uint32_t chat_id);
 void            mrmailbox_update_msg_state__                      (mrmailbox_t*, uint32_t msg_id, int state);
 void            mrmailbox_delete_msg_on_imap                      (mrmailbox_t* mailbox, mrjob_t* job);
-int             mrmailbox_mdn_from_ext__                          (mrmailbox_t*, uint32_t from_id, const char* rfc724_mid, uint32_t* ret_chat_id, uint32_t* ret_msg_id); /* returns 1 if an event should be send */
+int             mrmailbox_mdn_from_ext__                          (mrmailbox_t*, uint32_t from_id, const char* rfc724_mid, time_t, uint32_t* ret_chat_id, uint32_t* ret_msg_id); /* returns 1 if an event should be send */
 void            mrmailbox_send_mdn                                (mrmailbox_t*, mrjob_t* job);
 void            mrmailbox_markseen_msg_on_imap                    (mrmailbox_t* mailbox, mrjob_t* job);
 void            mrmailbox_markseen_mdn_on_imap                    (mrmailbox_t* mailbox, mrjob_t* job);
 int             mrmailbox_get_thread_index                        (void);
 uint32_t        mrmailbox_add_device_msg                          (mrmailbox_t*, uint32_t chat_id, const char* text);
 uint32_t        mrmailbox_add_device_msg__                        (mrmailbox_t*, uint32_t chat_id, const char* text, time_t timestamp);
+int             mrmailbox_add_contact_to_chat4                    (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id, int from_handshake);
+uint32_t        mrmailbox_get_chat_id_by_grpid__                  (mrmailbox_t*, const char* grpid, int* ret_blocked, int* ret_verified);
 
 
 /* library private: end-to-end-encryption */
@@ -137,12 +140,19 @@ uint32_t        mrmailbox_add_device_msg__                        (mrmailbox_t*,
 #define MR_MDNS_DEFAULT_ENABLED  1
 
 typedef struct mrmailbox_e2ee_helper_t {
+	// encryption
 	int   m_encryption_successfull;
 	void* m_cdata_to_free;
+
+	// decryption
+	int       m_encrypted;  // encrypted without problems
+	mrhash_t* m_signatures; // fingerprints of valid signatures
+	mrhash_t* m_gossipped_addr;
+
 } mrmailbox_e2ee_helper_t;
 
-void            mrmailbox_e2ee_encrypt      (mrmailbox_t*, const clist* recipients_addr, int force_unencrypted, int e2ee_guaranteed, struct mailmime* in_out_message, mrmailbox_e2ee_helper_t*);
-int             mrmailbox_e2ee_decrypt      (mrmailbox_t*, struct mailmime* in_out_message, int* ret_validation_errors, int* ret_degrade_event); /* returns 1 if sth. was decrypted, 0 in other cases */
+void            mrmailbox_e2ee_encrypt      (mrmailbox_t*, const clist* recipients_addr, int force_plaintext, int e2ee_guaranteed, int min_verified, struct mailmime* in_out_message, mrmailbox_e2ee_helper_t*);
+void            mrmailbox_e2ee_decrypt      (mrmailbox_t*, struct mailmime* in_out_message, mrmailbox_e2ee_helper_t*); /* returns 1 if sth. was decrypted, 0 in other cases */
 void            mrmailbox_e2ee_thanks       (mrmailbox_e2ee_helper_t*); /* frees data referenced by "mailmime" but not freed by mailmime_free(). After calling mre2ee_unhelp(), in_out_message cannot be used any longer! */
 int             mrmailbox_ensure_secret_key_exists (mrmailbox_t*); /* makes sure, the private key exists, needed only for exporting keys and the case no message was sent before */
 char*           mrmailbox_create_setup_code (mrmailbox_t*);
@@ -156,8 +166,11 @@ void            mrmailbox_free_ongoing      (mrmailbox_t*);
 
 
 /* library private: secure-join */
-int             mrmailbox_is_securejoin_handshake__  (mrmailbox_t*, mrmimeparser_t*); /* must be called from lock */
-void            mrmailbox_handle_securejoin_handshake(mrmailbox_t*, mrmimeparser_t*, uint32_t chat_id); /* must not be called from lock */
+#define         MR_IS_HANDSHAKE_CONTINUE_NORMAL_PROCESSING 1
+#define         MR_IS_HANDSHAKE_STOP_NORMAL_PROCESSING     2
+int             mrmailbox_handle_securejoin_handshake(mrmailbox_t*, mrmimeparser_t*, uint32_t contact_id);
+void            mrmailbox_handle_degrade_event       (mrmailbox_t*, mrapeerstate_t*);
+
 
 #define OPENPGP4FPR_SCHEME "OPENPGP4FPR:" /* yes: uppercase */
 

+ 262 - 177
deltachat-ios/libraries/deltachat-core/src/mrmailbox.c

@@ -817,7 +817,8 @@ void mrmailbox_heartbeat(mrmailbox_t* mailbox)
 }
 
 /**
- * Get a list of chats.
+ * Get a list of chats. The list can be filtered by query parameters.
+ * To get the chat messages, use mrmailbox_get_chat_msgs().
  *
  * @memberof mrmailbox_t
  *
@@ -830,18 +831,19 @@ void mrmailbox_heartbeat(mrmailbox_t* mailbox)
  *       chats
  *     - if the flag MR_GCL_NO_SPECIALS is set, deaddrop and archive link are not added
  *       to the list (may be used eg. for selecting chats on forwarding, the flag is
- *      F not needed when MR_GCL_ARCHIVED_ONLY is already set)
-
- * @param query An optional query for filtering the list.  Only chats matching this query
+ *       not needed when MR_GCL_ARCHIVED_ONLY is already set)
+ *
+ * @param query_str An optional query for filtering the list.  Only chats matching this query
  *     are returned.  Give NULL for no filtering.
  *
+ * @param query_id An optional contact ID for filtering the list.  Only chats including this contact ID
+ *     are returned.  Give 0 for no filtering.
+ *
  * @return A chatlist as an mrchatlist_t object. Must be freed using
  *     mrchatlist_unref() when no longer used
  */
-mrchatlist_t* mrmailbox_get_chatlist(mrmailbox_t* mailbox, int listflags, const char* query)
+mrchatlist_t* mrmailbox_get_chatlist(mrmailbox_t* mailbox, int listflags, const char* query_str, uint32_t query_id)
 {
-	clock_t       start = clock();
-
 	int success = 0;
 	int db_locked = 0;
 	mrchatlist_t* obj = mrchatlist_new(mailbox);
@@ -853,7 +855,7 @@ mrchatlist_t* mrmailbox_get_chatlist(mrmailbox_t* mailbox, int listflags, const
 	mrsqlite3_lock(mailbox->m_sql);
 	db_locked = 1;
 
-		if( !mrchatlist_load_from_db__(obj, listflags, query) ) {
+		if( !mrchatlist_load_from_db__(obj, listflags, query_str, query_id) ) {
 			goto cleanup;
 		}
 
@@ -862,8 +864,6 @@ mrchatlist_t* mrmailbox_get_chatlist(mrmailbox_t* mailbox, int listflags, const
 cleanup:
 	if( db_locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 
-	mrmailbox_log_info(mailbox, 0, "Chatlist created in %.3f ms.", (double)(clock()-start)*1000.0/CLOCKS_PER_SEC);
-
 	if( success ) {
 		return obj;
 	}
@@ -985,6 +985,32 @@ uint32_t mrmailbox_get_chat_id_by_contact_id(mrmailbox_t* mailbox, uint32_t cont
 }
 
 
+uint32_t mrmailbox_get_chat_id_by_grpid__(mrmailbox_t* mailbox, const char* grpid, int* ret_blocked, int* ret_verified)
+{
+	uint32_t      chat_id = 0;
+	sqlite3_stmt* stmt;
+
+	if(ret_blocked)  { *ret_blocked = 0;  }
+	if(ret_verified) { *ret_verified = 0; }
+
+	if( mailbox == NULL || grpid == NULL ) {
+		goto cleanup;
+	}
+
+	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_CHATS_WHERE_grpid,
+		"SELECT id, blocked, type FROM chats WHERE grpid=?;");
+	sqlite3_bind_text (stmt, 1, grpid, -1, SQLITE_STATIC);
+	if( sqlite3_step(stmt)==SQLITE_ROW ) {
+		                    chat_id      =  sqlite3_column_int(stmt, 0);
+		if(ret_blocked)  { *ret_blocked  =  sqlite3_column_int(stmt, 1); }
+		if(ret_verified) { *ret_verified = (sqlite3_column_int(stmt, 2)==MR_CHAT_TYPE_VERIFIED_GROUP); }
+	}
+
+cleanup:
+	return chat_id;
+}
+
+
 /**
  * Create a normal chat with a single user.  To create group chats,
  * see mrmailbox_create_group_chat().
@@ -1258,7 +1284,6 @@ cleanup:
  * @memberof mrmailbox_t
  *
  * @param mailbox The mailbox object as returned from mrmailbox_new().
- *
  * @param chat_id Chat ID to get the belonging contact IDs for.
  *
  * @return an array of contact IDs belonging to the chat; must be freed using mrarray_unref() when done.
@@ -1282,7 +1307,7 @@ mrarray_t* mrmailbox_get_chat_contacts(mrmailbox_t* mailbox, uint32_t chat_id)
 	mrsqlite3_lock(mailbox->m_sql);
 	locked = 1;
 
-		stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_c_FROM_chats_contacts_WHERE_c_ORDER_BY,
+		stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_contact_id_FROM_chats_contacts_WHERE_chat_id_ORDER_BY,
 			"SELECT cc.contact_id FROM chats_contacts cc"
 				" LEFT JOIN contacts c ON c.id=cc.contact_id"
 				" WHERE cc.chat_id=?"
@@ -1404,7 +1429,7 @@ mrarray_t* mrmailbox_get_chat_msgs(mrmailbox_t* mailbox, uint32_t chat_id, uint3
 					" LEFT JOIN contacts ON m.from_id=contacts.id"
 					" WHERE m.from_id!=" MR_STRINGIFY(MR_CONTACT_ID_SELF)
 					"   AND m.hidden=0 "
-					"   AND chats.blocked=2 "
+					"   AND chats.blocked=" MR_STRINGIFY(MR_CHAT_DEADDROP_BLOCKED)
 					"   AND contacts.blocked=0"
 					" ORDER BY m.timestamp,m.id;"); /* the list starts with the oldest message*/
 		}
@@ -1424,10 +1449,10 @@ mrarray_t* mrmailbox_get_chat_msgs(mrmailbox_t* mailbox, uint32_t chat_id, uint3
 			stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_i_FROM_msgs_LEFT_JOIN_contacts_WHERE_c,
 				"SELECT m.id, m.timestamp"
 					" FROM msgs m"
-					" LEFT JOIN contacts ct ON m.from_id=ct.id"
+					//" LEFT JOIN contacts ct ON m.from_id=ct.id"
 					" WHERE m.chat_id=? "
 					"   AND m.hidden=0 "
-					"   AND ct.blocked=0"
+					//"   AND ct.blocked=0" -- we hide blocked-contacts from starred and deaddrop, but we have to show them in groups (otherwise it may be hard to follow conversation, wa and tg do the same. however, maybe this needs discussion some time :)
 					" ORDER BY m.timestamp,m.id;"); /* the list starts with the oldest message*/
 			sqlite3_bind_int(stmt, 1, chat_id);
 		}
@@ -1759,7 +1784,7 @@ void mrmailbox_lookup_real_nchat_by_contact_id__(mrmailbox_t* mailbox, uint32_t
 			"SELECT c.id, c.blocked"
 			" FROM chats c"
 			" INNER JOIN chats_contacts j ON c.id=j.chat_id"
-			" WHERE c.type=" MR_STRINGIFY(MR_CHAT_TYPE_NORMAL) " AND c.id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) " AND j.contact_id=?;");
+			" WHERE c.type=" MR_STRINGIFY(MR_CHAT_TYPE_SINGLE) " AND c.id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) " AND j.contact_id=?;");
 	sqlite3_bind_int(stmt, 1, contact_id);
 
 	if( sqlite3_step(stmt) == SQLITE_ROW ) {
@@ -1805,7 +1830,7 @@ void mrmailbox_create_or_lookup_nchat_by_contact_id__(mrmailbox_t* mailbox, uint
 	chat_name = (contact->m_name&&contact->m_name[0])? contact->m_name : contact->m_addr;
 
 	/* create chat record */
-	q = sqlite3_mprintf("INSERT INTO chats (type, name, param, blocked) VALUES(%i, %Q, %Q, %i)", MR_CHAT_TYPE_NORMAL, chat_name,
+	q = sqlite3_mprintf("INSERT INTO chats (type, name, param, blocked) VALUES(%i, %Q, %Q, %i)", MR_CHAT_TYPE_SINGLE, chat_name,
 		contact_id==MR_CONTACT_ID_SELF? "K=1" : "", create_blocked);
 	assert( MRP_SELFTALK == 'K' );
 	stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, q);
@@ -1824,7 +1849,7 @@ void mrmailbox_create_or_lookup_nchat_by_contact_id__(mrmailbox_t* mailbox, uint
 	sqlite3_finalize(stmt);
 	stmt = NULL;
 
-	/* add contact IDs to the new chat record (may be replaced by mrmailbox_add_contact_to_chat__()) */
+	/* add contact IDs to the new chat record (may be replaced by mrmailbox_add_to_chat_contacts_table__()) */
 	q = sqlite3_mprintf("INSERT INTO chats_contacts (chat_id, contact_id) VALUES(%i, %i)", chat_id, contact_id);
 	stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, q);
 
@@ -2010,6 +2035,13 @@ void mrmailbox_delete_chat(mrmailbox_t* mailbox, uint32_t chat_id)
 		mrsqlite3_begin_transaction__(mailbox->m_sql);
 		pending_transaction = 1;
 
+			q3 = sqlite3_mprintf("DELETE FROM msgs_mdns WHERE msg_id IN (SELECT msg_id FROM msgs WHERE chat_id=%i);", chat_id);
+			if( !mrsqlite3_execute__(mailbox->m_sql, q3) ) {
+				goto cleanup;
+			}
+			sqlite3_free(q3);
+			q3 = NULL;
+
 			q3 = sqlite3_mprintf("DELETE FROM msgs WHERE chat_id=%i;", chat_id);
 			if( !mrsqlite3_execute__(mailbox->m_sql, q3) ) {
 				goto cleanup;
@@ -2233,7 +2265,12 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 	sqlite3_stmt* stmt;
 	uint32_t      msg_id = 0, to_id = 0;
 
-	if( chat->m_type==MR_CHAT_TYPE_GROUP && !mrmailbox_is_contact_in_chat__(mailbox, chat->m_id, MR_CONTACT_ID_SELF) ) {
+	if( !MR_CHAT_TYPE_CAN_SEND(chat->m_type) ) {
+		mrmailbox_log_error(mailbox, 0, "Cannot send to chat type #%i.", chat->m_type);
+		goto cleanup;
+	}
+
+	if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) && !mrmailbox_is_contact_in_chat__(mailbox, chat->m_id, MR_CONTACT_ID_SELF) ) {
 		mrmailbox_log_error(mailbox, MR_ERR_SELF_NOT_IN_GROUP, NULL);
 		goto cleanup;
 	}
@@ -2244,11 +2281,11 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 			mrmailbox_log_error(mailbox, 0, "Cannot send message, not configured successfully.");
 			goto cleanup;
 		}
-		rfc724_mid = mr_create_outgoing_rfc724_mid(chat->m_type==MR_CHAT_TYPE_GROUP? chat->m_grpid : NULL, from);
+		rfc724_mid = mr_create_outgoing_rfc724_mid(MR_CHAT_TYPE_IS_MULTI(chat->m_type)? chat->m_grpid : NULL, from);
 		free(from);
 	}
 
-	if( chat->m_type == MR_CHAT_TYPE_NORMAL )
+	if( chat->m_type == MR_CHAT_TYPE_SINGLE )
 	{
 		stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_c_FROM_chats_contacts_WHERE_c,
 			"SELECT contact_id FROM chats_contacts WHERE chat_id=?;");
@@ -2259,7 +2296,7 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 		}
 		to_id = sqlite3_column_int(stmt, 0);
 	}
-	else if( chat->m_type == MR_CHAT_TYPE_GROUP )
+	else if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 	{
 		if( mrparam_get_int(chat->m_param, MRP_UNPROMOTED, 0)==1 ) {
 			/* mark group as being no longer unpromoted */
@@ -2270,7 +2307,7 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 
 	/* check if we can guarantee E2EE for this message.  If we can, we won't send the message without E2EE later (because of a reset, changed settings etc. - messages may be delayed significally if there is no network present) */
 	int do_guarantee_e2ee = 0;
-	if( mailbox->m_e2ee_enabled && mrparam_get_int(msg->m_param, MRP_FORCE_UNENCRYPTED, 0)==0 )
+	if( mailbox->m_e2ee_enabled && mrparam_get_int(msg->m_param, MRP_FORCE_PLAINTEXT, 0)==0 )
 	{
 		int can_encrypt = 1, all_mutual = 1; /* be optimistic */
 		sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_p_FROM_chats_contacs_JOIN_contacts_peerstates_WHERE_cc,
@@ -2904,6 +2941,7 @@ void mrmailbox_set_group_explicitly_left__(mrmailbox_t* mailbox, const char* grp
 
 static int mrmailbox_real_group_exists__(mrmailbox_t* mailbox, uint32_t chat_id)
 {
+	// check if a group or a verified group exists under the given ID
 	sqlite3_stmt* stmt;
 	int           ret = 0;
 
@@ -2913,9 +2951,10 @@ static int mrmailbox_real_group_exists__(mrmailbox_t* mailbox, uint32_t chat_id)
 	}
 
 	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_chats_WHERE_id,
-		"SELECT id FROM chats WHERE id=? AND type=?;");
+		"SELECT id FROM chats "
+		" WHERE id=? "
+		"   AND (type=" MR_STRINGIFY(MR_CHAT_TYPE_GROUP) " OR type=" MR_STRINGIFY(MR_CHAT_TYPE_VERIFIED_GROUP) ");");
 	sqlite3_bind_int(stmt, 1, chat_id);
-	sqlite3_bind_int(stmt, 2, MR_CHAT_TYPE_GROUP);
 
 	if( sqlite3_step(stmt) == SQLITE_ROW ) {
 		ret = 1;
@@ -2925,7 +2964,7 @@ static int mrmailbox_real_group_exists__(mrmailbox_t* mailbox, uint32_t chat_id)
 }
 
 
-int mrmailbox_add_contact_to_chat__(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t contact_id)
+int mrmailbox_add_to_chat_contacts_table__(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t contact_id)
 {
 	/* add a contact to a chat; the function does not check the type or if any of the record exist or are already added to the chat! */
 	sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_chats_contacts,
@@ -2953,14 +2992,15 @@ int mrmailbox_add_contact_to_chat__(mrmailbox_t* mailbox, uint32_t chat_id, uint
  * @memberof mrmailbox_t
  *
  * @param mailbox Mailbox object as created by mrmailbox_new().
- *
+ * @param verified If set to 1 the function creates a secure verfied group.
+ *     Only secure-verified members are allowd in these groups and end-to-end-encryption is always enabled.
  * @param chat_name The name of the group chat to create.
  *     The name may be changed later using mrmailbox_set_chat_name().
  *     To find out the name of a group later, see mrchat_t::m_name
  *
  * @return The chat ID of the new group chat, 0 on errors.
  */
-uint32_t mrmailbox_create_group_chat(mrmailbox_t* mailbox, const char* chat_name)
+uint32_t mrmailbox_create_group_chat(mrmailbox_t* mailbox, int verified, const char* chat_name)
 {
 	uint32_t      chat_id = 0;
 	int           locked = 0;
@@ -2979,7 +3019,7 @@ uint32_t mrmailbox_create_group_chat(mrmailbox_t* mailbox, const char* chat_name
 
 		stmt = mrsqlite3_prepare_v2_(mailbox->m_sql,
 			"INSERT INTO chats (type, name, draft_timestamp, draft_txt, grpid, param) VALUES(?, ?, ?, ?, ?, 'U=1');" /*U=MRP_UNPROMOTED*/ );
-		sqlite3_bind_int  (stmt, 1, MR_CHAT_TYPE_GROUP);
+		sqlite3_bind_int  (stmt, 1, verified? MR_CHAT_TYPE_VERIFIED_GROUP : MR_CHAT_TYPE_GROUP);
 		sqlite3_bind_text (stmt, 2, chat_name, -1, SQLITE_STATIC);
 		sqlite3_bind_int64(stmt, 3, time(NULL));
 		sqlite3_bind_text (stmt, 4, draft_txt, -1, SQLITE_STATIC);
@@ -2992,7 +3032,7 @@ uint32_t mrmailbox_create_group_chat(mrmailbox_t* mailbox, const char* chat_name
 			goto cleanup;
 		}
 
-		if( mrmailbox_add_contact_to_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF) ) {
+		if( mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, MR_CONTACT_ID_SELF) ) {
 			goto cleanup;
 		}
 
@@ -3218,31 +3258,14 @@ int mrmailbox_is_contact_in_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32_
 }
 
 
-/**
- * Add a member to a 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.
- *
- * Sends out #MR_EVENT_CHAT_MODIFIED and #MR_EVENT_MSGS_CHANGED if a status message was sent.
- *
- * @memberof mrmailbox_t
- *
- * @param mailbox Mailbox object as created by mrmailbox_new().
- *
- * @param chat_id The chat ID to add the contact to.  Must be a group chat.
- *
- * @param contact_id The contact ID to add to the chat.
- *
- * @return 1=member added to group, 0=error
- */
-int mrmailbox_add_contact_to_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t contact_id /*may be MR_CONTACT_ID_SELF*/)
+int mrmailbox_add_contact_to_chat4(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t contact_id, int from_handshake)
 {
-	int          success = 0, locked = 0;
-	mrcontact_t* contact = mrmailbox_get_contact(mailbox, contact_id);
-	mrchat_t*    chat = mrchat_new(mailbox);
-	mrmsg_t*     msg = mrmsg_new();
-	char*        self_addr = NULL;
+	int             success   = 0, locked = 0;
+	mrcontact_t*    contact   = mrmailbox_get_contact(mailbox, contact_id);
+	mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
+	mrchat_t*       chat      = mrchat_new(mailbox);
+	mrmsg_t*        msg       = mrmsg_new();
+	char*           self_addr = NULL;
 
 	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || contact == NULL || chat_id <= MR_CHAT_ID_LAST_SPECIAL ) {
 		goto cleanup;
@@ -3262,18 +3285,40 @@ int mrmailbox_add_contact_to_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32
 			goto cleanup; /* we shoud respect this - whatever we send to the group, it gets discarded anyway! */
 		}
 
+		if( from_handshake && mrparam_get_int(chat->m_param, MRP_UNPROMOTED, 0)==1 ) {
+			// after a handshake, force sending the `Chat-Group-Member-Added` message
+			mrparam_set(chat->m_param, MRP_UNPROMOTED, NULL);
+			mrchat_update_param__(chat);
+		}
+
 		self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", "");
 		if( strcasecmp(contact->m_addr, self_addr)==0 ) {
 			goto cleanup; /* ourself is added using MR_CONTACT_ID_SELF, do not add it explicitly. if SELF is not in the group, members cannot be added at all. */
 		}
 
-		if( 1==mrmailbox_is_contact_in_chat__(mailbox, chat_id, contact_id) ) {
-			success = 1;
-			goto cleanup;
+		if( mrmailbox_is_contact_in_chat__(mailbox, chat_id, contact_id) )
+		{
+			if( !from_handshake ) {
+				success = 1;
+				goto cleanup;
+			}
+			// else continue and send status mail
 		}
+		else
+		{
+			if( !mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, contact->m_addr) ) {
+				goto cleanup;
+			}
 
-		if( 0==mrmailbox_add_contact_to_chat__(mailbox, chat_id, contact_id) ) {
-			goto cleanup;
+			if( chat->m_type==MR_CHAT_TYPE_VERIFIED_GROUP
+			 && mrcontact_is_verified__(contact, peerstate)!=MRV_BIDIRECTIONAL ) {
+				mrmailbox_log_error(mailbox, 0, "Only bidirectional verified contacts can be added to verfied groups.");
+				goto cleanup;
+			}
+
+			if( 0==mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, contact_id) ) {
+				goto cleanup;
+			}
 		}
 
 	mrsqlite3_unlock(mailbox->m_sql);
@@ -3286,6 +3331,7 @@ int mrmailbox_add_contact_to_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32
 		msg->m_text = mrstock_str_repl_string(MR_STR_MSGADDMEMBER, (contact->m_authname&&contact->m_authname[0])? contact->m_authname : contact->m_addr);
 		mrparam_set_int(msg->m_param, MRP_CMD,       MR_CMD_MEMBER_ADDED_TO_GROUP);
 		mrparam_set    (msg->m_param, MRP_CMD_PARAM, contact->m_addr);
+		mrparam_set_int(msg->m_param, MRP_CMD_PARAM2,from_handshake); // combine the Secure-Join protocol headers with the Chat-Group-Member-Added header
 		msg->m_id = mrmailbox_send_msg_object(mailbox, chat_id, msg);
 		mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, msg->m_id);
 	}
@@ -3297,12 +3343,39 @@ cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 	mrchat_unref(chat);
 	mrcontact_unref(contact);
+	mrapeerstate_unref(peerstate);
 	mrmsg_unref(msg);
 	free(self_addr);
 	return success;
 }
 
 
+/**
+ * Add a member to a 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.
+ *
+ * If the group is a verified group, only verified contacts can be added to the group.
+ *
+ * Sends out #MR_EVENT_CHAT_MODIFIED and #MR_EVENT_MSGS_CHANGED if a status message was sent.
+ *
+ * @memberof mrmailbox_t
+ *
+ * @param mailbox Mailbox object as created by mrmailbox_new().
+ *
+ * @param chat_id The chat ID to add the contact to.  Must be a group chat.
+ *
+ * @param contact_id The contact ID to add to the chat.
+ *
+ * @return 1=member added to group, 0=error
+ */
+int mrmailbox_add_contact_to_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t contact_id /*may be MR_CONTACT_ID_SELF*/)
+{
+	return mrmailbox_add_contact_to_chat4(mailbox, chat_id, contact_id, 0);
+}
+
+
 /**
  * Remove a member from a group.
  *
@@ -3447,6 +3520,8 @@ uint32_t mrmailbox_add_or_lookup_contact__( mrmailbox_t* mailbox,
                                            int          origin,
                                            int*         sth_modified )
 {
+	#define       CONTACT_MODIFIED 1
+	#define       CONTACT_CREATED  2
 	sqlite3_stmt* stmt;
 	uint32_t      row_id = 0;
 	int           dummy;
@@ -3525,12 +3600,12 @@ uint32_t mrmailbox_add_or_lookup_contact__( mrmailbox_t* mailbox,
 				stmt = mrsqlite3_predefine__(mailbox->m_sql, UPDATE_chats_SET_n_WHERE_c,
 					"UPDATE chats SET name=? WHERE type=? AND id IN(SELECT chat_id FROM chats_contacts WHERE contact_id=?);");
 				sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
-				sqlite3_bind_int (stmt, 2, MR_CHAT_TYPE_NORMAL);
+				sqlite3_bind_int (stmt, 2, MR_CHAT_TYPE_SINGLE);
 				sqlite3_bind_int (stmt, 3, row_id);
 				sqlite3_step     (stmt);
 			}
 
-			*sth_modified = 1;
+			*sth_modified = CONTACT_MODIFIED;
 		}
 	}
 	else
@@ -3543,7 +3618,7 @@ uint32_t mrmailbox_add_or_lookup_contact__( mrmailbox_t* mailbox,
 		if( sqlite3_step(stmt) == SQLITE_DONE )
 		{
 			row_id = sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj);
-			*sth_modified = 1;
+			*sth_modified = CONTACT_CREATED;
 		}
 		else
 		{
@@ -3640,6 +3715,7 @@ cleanup:
 uint32_t mrmailbox_create_contact(mrmailbox_t* mailbox, const char* name, const char* addr)
 {
 	uint32_t contact_id = 0;
+	int      sth_modified = 0;
 
 	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || addr == NULL || addr[0]==0 ) {
 		goto cleanup;
@@ -3647,11 +3723,11 @@ uint32_t mrmailbox_create_contact(mrmailbox_t* mailbox, const char* name, const
 
 	mrsqlite3_lock(mailbox->m_sql);
 
-		contact_id = mrmailbox_add_or_lookup_contact__(mailbox, name, addr, MR_ORIGIN_MANUALLY_CREATED, NULL);
+		contact_id = mrmailbox_add_or_lookup_contact__(mailbox, name, addr, MR_ORIGIN_MANUALLY_CREATED, &sth_modified);
 
 	mrsqlite3_unlock(mailbox->m_sql);
 
-	mailbox->m_cb(mailbox, MR_EVENT_CONTACTS_CHANGED, 0, 0);
+	mailbox->m_cb(mailbox, MR_EVENT_CONTACTS_CHANGED, sth_modified==CONTACT_CREATED? contact_id : 0, 0);
 
 cleanup:
 	return contact_id;
@@ -3729,13 +3805,17 @@ cleanup:
  *
  * @param mailbox The mailbox object as created by mrmailbox_new().
  *
+ * @param listflags A combination of flags:
+ *     - if the flag MR_GCL_ADD_SELF is set, SELF is added to the list unless filtered by other parameters
+ *     - if the flag MR_GCL_VERIFIED_ONLY is set, only verified contacts are returned.
+ *       if MR_GCL_VERIFIED_ONLY is not set, verified and unverified contacts are returned.
  * @param query A string to filter the list.  Typically used to implement an
  *     incremental search.  NULL for no filtering.
  *
  * @return An array containing all contact IDs.  Must be mrarray_unref()'d
  *     after usage.
  */
-mrarray_t* mrmailbox_get_known_contacts(mrmailbox_t* mailbox, const char* query)
+mrarray_t* mrmailbox_get_contacts(mrmailbox_t* mailbox, uint32_t listflags, const char* query)
 {
 	int           locked = 0;
 	char*         self_addr = NULL;
@@ -3755,22 +3835,27 @@ mrarray_t* mrmailbox_get_known_contacts(mrmailbox_t* mailbox, const char* query)
 
 		self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", ""); /* we add MR_CONTACT_ID_SELF explicitly; so avoid doubles if the address is present as a normal entry for some case */
 
-		if( query )
+		if( (listflags&MR_GCL_VERIFIED_ONLY) || query )
 		{
-			if( (s3strLikeCmd=sqlite3_mprintf("%%%s%%", query))==NULL ) {
+			if( (s3strLikeCmd=sqlite3_mprintf("%%%s%%", query? query : ""))==NULL ) {
 				goto cleanup;
 			}
 			stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_contacts_WHERE_query_ORDER_BY,
-				"SELECT id FROM contacts"
-					" WHERE addr!=? AND id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) " AND origin>=" MR_STRINGIFY(MR_ORIGIN_MIN_CONTACT_LIST) " AND blocked=0 AND (name LIKE ? OR addr LIKE ?)" /* see comments in mrmailbox_search_msgs() about the LIKE operator */
-					" ORDER BY LOWER(name||addr),id;");
+				"SELECT c.id FROM contacts c"
+					" LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
+					" WHERE c.addr!=? AND c.id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) " AND c.origin>=" MR_STRINGIFY(MR_ORIGIN_MIN_CONTACT_LIST) " AND c.blocked=0 AND (c.name LIKE ? OR c.addr LIKE ?)" /* see comments in mrmailbox_search_msgs() about the LIKE operator */
+					" AND (ps.public_key_verified=? OR ps.gossip_key_verified=? OR 1=?) "
+					" ORDER BY LOWER(c.name||c.addr),c.id;");
 			sqlite3_bind_text(stmt, 1, self_addr, -1, SQLITE_STATIC);
 			sqlite3_bind_text(stmt, 2, s3strLikeCmd, -1, SQLITE_STATIC);
 			sqlite3_bind_text(stmt, 3, s3strLikeCmd, -1, SQLITE_STATIC);
+			sqlite3_bind_int (stmt, 4, (listflags&MR_GCL_VERIFIED_ONLY)? MRV_BIDIRECTIONAL : 0);
+			sqlite3_bind_int (stmt, 5, (listflags&MR_GCL_VERIFIED_ONLY)? MRV_BIDIRECTIONAL : 0);
+			sqlite3_bind_int (stmt, 6, (listflags&MR_GCL_VERIFIED_ONLY)? 0/*force checking for MRV_BIDIRECTIONAL*/ : 1/*force statement being always true*/);
 
 			self_name  = mrsqlite3_get_config__(mailbox->m_sql, "displayname", "");
 			self_name2 = mrstock_str(MR_STR_SELF);
-			if( mr_str_contains(self_addr, query) || mr_str_contains(self_name, query) || mr_str_contains(self_name2, query) ) {
+			if( query==NULL || mr_str_contains(self_addr, query) || mr_str_contains(self_name, query) || mr_str_contains(self_name2, query) ) {
 				add_self = 1;
 			}
 		}
@@ -3793,7 +3878,7 @@ mrarray_t* mrmailbox_get_known_contacts(mrmailbox_t* mailbox, const char* query)
 	locked = 0;
 
 	/* to the end of the list, add self - this is to be in sync with member lists and to allow the user to start a self talk */
-	if( add_self ) {
+	if( (listflags&MR_GCL_ADD_SELF) && add_self ) {
 		mrarray_add_id(ret, MR_CONTACT_ID_SELF);
 	}
 
@@ -3882,7 +3967,7 @@ cleanup:
 
 
 /**
- * Get a single contact object.  For a list, see eg. mrmailbox_get_known_contacts().
+ * Get a single contact object.  For a list, see eg. mrmailbox_get_contacts().
  *
  * For contact MR_CONTACT_ID_SELF (1), the function returns the name
  * MR_STR_SELF (typically "Me" in the selected language) and the email address
@@ -4020,7 +4105,7 @@ void mrmailbox_block_contact(mrmailbox_t* mailbox, uint32_t contact_id, int new_
 				stmt = mrsqlite3_predefine__(mailbox->m_sql, UPDATE_chats_SET_blocked_WHERE_contact_id,
 					"UPDATE chats SET blocked=? WHERE type=? AND id IN (SELECT chat_id FROM chats_contacts WHERE contact_id=?);");
 				sqlite3_bind_int(stmt, 1, new_blocking);
-				sqlite3_bind_int(stmt, 2, MR_CHAT_TYPE_NORMAL);
+				sqlite3_bind_int(stmt, 2, MR_CHAT_TYPE_SINGLE);
 				sqlite3_bind_int(stmt, 3, contact_id);
 				if( sqlite3_step(stmt)!=SQLITE_DONE ) {
 					goto cleanup;
@@ -4049,12 +4134,23 @@ cleanup:
 }
 
 
-static void cat_fingerprint(mrstrbuilder_t* ret, const char* addr, const char* fingerprint_str)
+static void cat_fingerprint(mrstrbuilder_t* ret, const char* addr, const char* fingerprint_verified, const char* fingerprint_unverified)
 {
+	mrstrbuilder_cat(ret, "\n\n");
 	mrstrbuilder_cat(ret, addr);
 	mrstrbuilder_cat(ret, ":\n");
-	mrstrbuilder_cat(ret, fingerprint_str);
-	mrstrbuilder_cat(ret, "\n\n");
+	mrstrbuilder_cat(ret, (fingerprint_verified&&fingerprint_verified[0])? fingerprint_verified : fingerprint_unverified);
+
+	if( fingerprint_verified && fingerprint_verified[0]
+	 && fingerprint_unverified && fingerprint_unverified[0]
+	 && strcmp(fingerprint_verified, fingerprint_unverified)!=0 ) {
+		// might be that for verified chats the - older - verified gossiped key is used
+		// and for normal chats the - newer - unverified key :/
+		mrstrbuilder_cat(ret, "\n\n");
+		mrstrbuilder_cat(ret, addr);
+		mrstrbuilder_cat(ret, " (alternative):\n");
+		mrstrbuilder_cat(ret, fingerprint_unverified);
+	}
 }
 
 
@@ -4074,15 +4170,13 @@ static void cat_fingerprint(mrstrbuilder_t* ret, const char* addr, const char* f
 char* mrmailbox_get_contact_encrinfo(mrmailbox_t* mailbox, uint32_t contact_id)
 {
 	int             locked = 0;
-	int             e2ee_enabled = 0;
-	int             explain_id = 0;
 	mrloginparam_t* loginparam = mrloginparam_new();
 	mrcontact_t*    contact = mrcontact_new(mailbox);
 	mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
-	int             peerstate_ok = 0;
 	mrkey_t*        self_key = mrkey_new();
-	char*           fingerprint_str_self = NULL;
-	char*           fingerprint_str_other = NULL;
+	char*           fingerprint_self = NULL;
+	char*           fingerprint_other_verified = NULL;
+	char*           fingerprint_other_unverified = NULL;
 	char*           p;
 
 	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC ) {
@@ -4098,54 +4192,19 @@ char* mrmailbox_get_contact_encrinfo(mrmailbox_t* mailbox, uint32_t contact_id)
 		if( !mrcontact_load_from_db__(contact, mailbox->m_sql, contact_id) ) {
 			goto cleanup;
 		}
-		peerstate_ok = mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, contact->m_addr);
+		mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, contact->m_addr);
 		mrloginparam_read__(loginparam, mailbox->m_sql, "configured_");
-		e2ee_enabled = mailbox->m_e2ee_enabled;
 
 		mrkey_load_self_public__(self_key, loginparam->m_addr, mailbox->m_sql);
 
 	mrsqlite3_unlock(mailbox->m_sql);
 	locked = 0;
 
-	/* show the encryption that would be used for the next outgoing message */
-	if( e2ee_enabled
-	 && peerstate_ok
-	 && peerstate->m_prefer_encrypt==MRA_PE_MUTUAL
-	 && peerstate->m_public_key!=NULL )
-	{
-		/* e2e fine and used */
-		p = mrstock_str(MR_STR_ENCR_E2E); mrstrbuilder_cat(&ret, p); free(p);
-		explain_id = MR_STR_E2E_FINE;
-	}
-	else
+	if( mrapeerstate_peek_key(peerstate, MRV_NOT_VERIFIED) )
 	{
-		/* e2e not used ... first, show status quo ... */
-		if( !(loginparam->m_server_flags&MR_IMAP_SOCKET_PLAIN)
-		 && !(loginparam->m_server_flags&MR_SMTP_SOCKET_PLAIN) )
-		{
-			p = mrstock_str(MR_STR_ENCR_TRANSP); mrstrbuilder_cat(&ret, p); free(p);
-		}
-		else
-		{
-			p = mrstock_str(MR_STR_ENCR_NONE); mrstrbuilder_cat(&ret, p); free(p);
-		}
+		// E2E available :)
+		p = mrstock_str(peerstate->m_prefer_encrypt == MRA_PE_MUTUAL? MR_STR_E2E_PREFERRED : MR_STR_E2E_AVAILABLE); mrstrbuilder_cat(&ret, p); free(p);
 
-		/* ... and then explain why we cannot use e2e */
-		if( !e2ee_enabled ) {
-			explain_id = MR_STR_E2E_DIS_BY_YOU;
-		}
-		else if( peerstate_ok && mrapeerstate_peek_key(peerstate) ) {
-			explain_id = MR_STR_E2E_DIS_BY_RCPT; /* this includes the situation where we have only a gossip_key and no direct contact to the recipient */
-		}
-		else {
-			explain_id = MR_STR_E2E_NO_AUTOCRYPT;
-		}
-	}
-
-	/* show fingerprints for comparison (sorted by email-address to make a device-side-by-side comparison easier) */
-	if( peerstate_ok
-	 && mrapeerstate_peek_key(peerstate) )
-	{
 		if( self_key->m_binary == NULL ) {
 			mrpgp_rand_seed(mailbox, peerstate->m_addr, strlen(peerstate->m_addr) /*just some random data*/);
 			mrmailbox_ensure_secret_key_exists(mailbox);
@@ -4158,35 +4217,44 @@ char* mrmailbox_get_contact_encrinfo(mrmailbox_t* mailbox, uint32_t contact_id)
 
 		mrstrbuilder_cat(&ret, " ");
 		p = mrstock_str(MR_STR_FINGERPRINTS); mrstrbuilder_cat(&ret, p); free(p);
-		mrstrbuilder_cat(&ret, ":\n\n");
+		mrstrbuilder_cat(&ret, ":");
 
-		fingerprint_str_self = mrkey_get_formatted_fingerprint(self_key);
-		fingerprint_str_other = mrkey_get_formatted_fingerprint(mrapeerstate_peek_key(peerstate));
+		fingerprint_self = mrkey_get_formatted_fingerprint(self_key);
+		fingerprint_other_verified = mrkey_get_formatted_fingerprint(mrapeerstate_peek_key(peerstate, MRV_BIDIRECTIONAL));
+		fingerprint_other_unverified = mrkey_get_formatted_fingerprint(mrapeerstate_peek_key(peerstate, MRV_NOT_VERIFIED));
 
 		if( strcmp(loginparam->m_addr, peerstate->m_addr)<0 ) {
-			cat_fingerprint(&ret, loginparam->m_addr, fingerprint_str_self);
-			cat_fingerprint(&ret, peerstate->m_addr, fingerprint_str_other);
+			cat_fingerprint(&ret, loginparam->m_addr, fingerprint_self, NULL);
+			cat_fingerprint(&ret, peerstate->m_addr, fingerprint_other_verified, fingerprint_other_unverified);
 		}
 		else {
-			cat_fingerprint(&ret, peerstate->m_addr, fingerprint_str_other);
-			cat_fingerprint(&ret, loginparam->m_addr, fingerprint_str_self);
+			cat_fingerprint(&ret, peerstate->m_addr, fingerprint_other_verified, fingerprint_other_unverified);
+			cat_fingerprint(&ret, loginparam->m_addr, fingerprint_self, NULL);
 		}
 	}
 	else
 	{
-		mrstrbuilder_cat(&ret, "\n\n");
+		// No E2E available
+		if( !(loginparam->m_server_flags&MR_IMAP_SOCKET_PLAIN)
+		 && !(loginparam->m_server_flags&MR_SMTP_SOCKET_PLAIN) )
+		{
+			p = mrstock_str(MR_STR_ENCR_TRANSP); mrstrbuilder_cat(&ret, p); free(p);
+		}
+		else
+		{
+			p = mrstock_str(MR_STR_ENCR_NONE); mrstrbuilder_cat(&ret, p); free(p);
+		}
 	}
 
-	p = mrstock_str(explain_id); mrstrbuilder_cat(&ret, p); free(p);
-
 cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 	mrapeerstate_unref(peerstate);
 	mrcontact_unref(contact);
 	mrloginparam_unref(loginparam);
 	mrkey_unref(self_key);
-	free(fingerprint_str_self);
-	free(fingerprint_str_other);
+	free(fingerprint_self);
+	free(fingerprint_other_verified);
+	free(fingerprint_other_unverified);
 	return ret.m_buf;
 }
 
@@ -4494,23 +4562,40 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 		mr_trim(rawtxt);
 		mr_truncate_str(rawtxt, MR_MAX_GET_INFO_LEN);
 
-	mrsqlite3_unlock(mailbox->m_sql);
-	locked = 0;
+		/* add time */
+		mrstrbuilder_cat(&ret, "Sent: ");
+		p = mr_timestamp_to_str(mrmsg_get_timestamp(msg)); mrstrbuilder_cat(&ret, p); free(p);
+		mrstrbuilder_cat(&ret, "\n");
 
-	/* add time */
-	mrstrbuilder_cat(&ret, "Sent: ");
-	p = mr_timestamp_to_str(mrmsg_get_timestamp(msg)); mrstrbuilder_cat(&ret, p); free(p);
-	mrstrbuilder_cat(&ret, "\n");
+		if( msg->m_from_id != MR_CONTACT_ID_SELF ) {
+			mrstrbuilder_cat(&ret, "Received: ");
+			p = mr_timestamp_to_str(msg->m_timestamp_rcvd? msg->m_timestamp_rcvd : msg->m_timestamp); mrstrbuilder_cat(&ret, p); free(p);
+			mrstrbuilder_cat(&ret, "\n");
+		}
 
-	if( msg->m_from_id != MR_CONTACT_ID_SELF ) {
-		mrstrbuilder_cat(&ret, "Received: ");
-		p = mr_timestamp_to_str(msg->m_timestamp_rcvd? msg->m_timestamp_rcvd : msg->m_timestamp); mrstrbuilder_cat(&ret, p); free(p);
-		mrstrbuilder_cat(&ret, "\n");
-	}
+		if( msg->m_from_id == MR_CONTACT_ID_DEVICE || msg->m_to_id == MR_CONTACT_ID_DEVICE ) {
+			goto cleanup; // device-internal message, no further details needed
+		}
 
-	if( msg->m_from_id == MR_CONTACT_ID_DEVICE || msg->m_to_id == MR_CONTACT_ID_DEVICE ) {
-		goto cleanup; // device-internal message, no further details needed
-	}
+		/* add mdn's time and readers */
+		stmt = mrsqlite3_prepare_v2_(mailbox->m_sql,
+			"SELECT contact_id, timestamp_sent FROM msgs_mdns WHERE msg_id=?;");
+		sqlite3_bind_int (stmt, 1, msg_id);
+		while( sqlite3_step(stmt) == SQLITE_ROW ) {
+			mrstrbuilder_cat(&ret, "Read: ");
+			p = mr_timestamp_to_str(sqlite3_column_int64(stmt, 1)); mrstrbuilder_cat(&ret, p); free(p);
+			mrstrbuilder_cat(&ret, " by ");
+
+			mrcontact_t* contact = mrcontact_new(mailbox);
+				mrcontact_load_from_db__(contact, mailbox->m_sql, sqlite3_column_int64(stmt, 0));
+				p = mrcontact_get_display_name(contact); mrstrbuilder_cat(&ret, p); free(p);
+			mrcontact_unref(contact);
+			mrstrbuilder_cat(&ret, "\n");
+		}
+		sqlite3_finalize(stmt);
+
+	mrsqlite3_unlock(mailbox->m_sql);
+	locked = 0;
 
 	/* add state */
 	p = NULL;
@@ -4530,14 +4615,8 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	p = NULL;
 	int e2ee_errors;
 	if( (e2ee_errors=mrparam_get_int(msg->m_param, MRP_ERRONEOUS_E2EE, 0)) ) {
-		if( e2ee_errors&MR_VALIDATE_BAD_SIGNATURE/* check worst errors first */ ) {
-			p = safe_strdup("Encrypted, bad signature");
-		}
-		else if( e2ee_errors&MR_VALIDATE_UNKNOWN_SIGNATURE ) {
-			p = safe_strdup("Encrypted, unknown signature");
-		}
-		else {
-			p = safe_strdup("Encrypted, no signature");
+		if( e2ee_errors&MRE2EE_NO_VALID_SIGNATURE ) {
+			p = safe_strdup("Encrypted, no valid signature");
 		}
 	}
 	else if( mrparam_get_int(msg->m_param, MRP_GUARANTEE_E2EE, 0) ) {
@@ -4560,7 +4639,7 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	/* add file info */
 	char* file = mrparam_get(msg->m_param, MRP_FILE, NULL);
 	if( file ) {
-		p = mr_mprintf("File: %s, %i bytes\n", file, (int)mr_get_filebytes(file)); mrstrbuilder_cat(&ret, p); free(p);
+		p = mr_mprintf("\nFile: %s, %i bytes\n", file, (int)mr_get_filebytes(file)); mrstrbuilder_cat(&ret, p); free(p);
 	}
 
 	if( msg->m_type != MR_MSG_TEXT ) {
@@ -4678,7 +4757,7 @@ void mrmailbox_forward_msgs(mrmailbox_t* mailbox, const uint32_t* msg_ids, int m
 
 			mrparam_set_int(msg->m_param, MRP_FORWARDED, 1);
 			mrparam_set    (msg->m_param, MRP_GUARANTEE_E2EE, NULL);
-			mrparam_set    (msg->m_param, MRP_FORCE_UNENCRYPTED, NULL);
+			mrparam_set    (msg->m_param, MRP_FORCE_PLAINTEXT, NULL);
 
 			uint32_t new_msg_id = mrmailbox_send_msg_i__(mailbox, chat, msg, curr_timestamp++);
 			carray_add(created_db_entries, (void*)(uintptr_t)chat_id, NULL);
@@ -4801,7 +4880,13 @@ void mrmailbox_delete_msg_on_imap(mrmailbox_t* mailbox, mrjob_t* job)
 	mrsqlite3_lock(mailbox->m_sql);
 	locked = 1;
 
-		sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, DELETE_FROM_msgs_WHERE_id, "DELETE FROM msgs WHERE id=?;");
+		sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, DELETE_FROM_msgs_WHERE_id,
+			"DELETE FROM msgs WHERE id=?;");
+		sqlite3_bind_int(stmt, 1, msg->m_id);
+		sqlite3_step(stmt);
+
+		stmt = mrsqlite3_predefine__(mailbox->m_sql, DELETE_FROM_msgs_mdns_WHERE_m,
+			"DELETE FROM msgs_mdns WHERE msg_id=?;");
 		sqlite3_bind_int(stmt, 1, msg->m_id);
 		sqlite3_step(stmt);
 
@@ -5067,7 +5152,7 @@ cleanup:
 }
 
 
-int mrmailbox_mdn_from_ext__(mrmailbox_t* mailbox, uint32_t from_id, const char* rfc724_mid,
+int mrmailbox_mdn_from_ext__(mrmailbox_t* mailbox, uint32_t from_id, const char* rfc724_mid, time_t timestamp_sent,
                                      uint32_t* ret_chat_id,
                                      uint32_t* ret_msg_id)
 {
@@ -5095,29 +5180,34 @@ int mrmailbox_mdn_from_ext__(mrmailbox_t* mailbox, uint32_t from_id, const char*
 		return 0; /* eg. already marked as MDNS_RCVD. however, it is importent, that the message ID is set above as this will allow the caller eg. to move the message away */
 	}
 
-	/* normal chat? that's quite easy. */
-	if( chat_type == MR_CHAT_TYPE_NORMAL )
-	{
-		mrmailbox_update_msg_state__(mailbox, *ret_msg_id, MR_STATE_OUT_MDN_RCVD);
-		return 1; /* send event about new state */
-	}
-
-	/* group chat: collect receipt senders */
-	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_c_FROM_msgs_mdns_WHERE_mc, "SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;");
+	// collect receipt senders, we do this also for normal chats as we may want to show the timestamp
+	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_c_FROM_msgs_mdns_WHERE_mc,
+		"SELECT contact_id FROM msgs_mdns WHERE msg_id=? AND contact_id=?;");
 	sqlite3_bind_int(stmt, 1, *ret_msg_id);
 	sqlite3_bind_int(stmt, 2, from_id);
 	if( sqlite3_step(stmt) != SQLITE_ROW ) {
-		stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_mdns, "INSERT INTO msgs_mdns (msg_id, contact_id) VALUES (?, ?);");
-		sqlite3_bind_int(stmt, 1, *ret_msg_id);
-		sqlite3_bind_int(stmt, 2, from_id);
+		stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_mdns,
+			"INSERT INTO msgs_mdns (msg_id, contact_id, timestamp_sent) VALUES (?, ?, ?);");
+		sqlite3_bind_int  (stmt, 1, *ret_msg_id);
+		sqlite3_bind_int  (stmt, 2, from_id);
+		sqlite3_bind_int64(stmt, 3, timestamp_sent);
 		sqlite3_step(stmt);
 	}
 
-	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_COUNT_FROM_msgs_mdns_WHERE_m, "SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;");
+	// Normal chat? that's quite easy.
+	if( chat_type == MR_CHAT_TYPE_SINGLE ) {
+		mrmailbox_update_msg_state__(mailbox, *ret_msg_id, MR_STATE_OUT_MDN_RCVD);
+		return 1; /* send event about new state */
+	}
+
+	// Group chat: get the number of receipt senders
+	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_COUNT_FROM_msgs_mdns_WHERE_m,
+		"SELECT COUNT(*) FROM msgs_mdns WHERE msg_id=?;");
 	sqlite3_bind_int(stmt, 1, *ret_msg_id);
 	if( sqlite3_step(stmt) != SQLITE_ROW ) {
 		return 0; /* error */
 	}
+	int ist_cnt  = sqlite3_column_int(stmt, 0);
 
 	/*
 	Groupsize:  Min. MDNs
@@ -5131,17 +5221,12 @@ int mrmailbox_mdn_from_ext__(mrmailbox_t* mailbox, uint32_t from_id, const char*
 
 	(S=Sender, R=Recipient)
 	*/
-	int ist_cnt  = sqlite3_column_int(stmt, 0);
 	int soll_cnt = (mrmailbox_get_chat_contact_count__(mailbox, *ret_chat_id)+1/*for rounding, SELF is already included!*/) / 2;
 	if( ist_cnt < soll_cnt ) {
 		return 0; /* wait for more receipts */
 	}
 
 	/* got enough receipts :-) */
-	stmt = mrsqlite3_predefine__(mailbox->m_sql, DELETE_FROM_msgs_mdns_WHERE_m, "DELETE FROM msgs_mdns WHERE msg_id=?;");
-	sqlite3_bind_int(stmt, 1, *ret_msg_id);
-	sqlite3_step(stmt);
-
 	mrmailbox_update_msg_state__(mailbox, *ret_msg_id, MR_STATE_OUT_MDN_RCVD);
 	return 1;
 }

+ 12 - 6
deltachat-ios/libraries/deltachat-core/src/mrmailbox.h

@@ -28,7 +28,7 @@ extern "C" {
 
 
 #define MR_VERSION_MAJOR    0
-#define MR_VERSION_MINOR    16
+#define MR_VERSION_MINOR    17
 #define MR_VERSION_REVISION 0
 
 
@@ -221,7 +221,7 @@ char*           mrmailbox_get_info          (mrmailbox_t*);
 /* Handle chatlists */
 #define         MR_GCL_ARCHIVED_ONLY        0x01
 #define         MR_GCL_NO_SPECIALS          0x02
-mrchatlist_t*   mrmailbox_get_chatlist      (mrmailbox_t*, int flags, const char* query);
+mrchatlist_t*   mrmailbox_get_chatlist      (mrmailbox_t*, int flags, const char* query_str, uint32_t query_id);
 
 
 /* Handle chats */
@@ -257,7 +257,7 @@ mrchat_t*       mrmailbox_get_chat          (mrmailbox_t*, uint32_t chat_id);
 
 
 /* Handle group chats */
-uint32_t        mrmailbox_create_group_chat        (mrmailbox_t*, const char* name);
+uint32_t        mrmailbox_create_group_chat        (mrmailbox_t*, int verified, const char* name);
 int             mrmailbox_is_contact_in_chat       (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
 int             mrmailbox_add_contact_to_chat      (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
 int             mrmailbox_remove_contact_from_chat (mrmailbox_t*, uint32_t chat_id, uint32_t contact_id);
@@ -278,7 +278,11 @@ mrmsg_t*        mrmailbox_get_msg           (mrmailbox_t*, uint32_t msg_id);
 /* Handle contacts */
 uint32_t        mrmailbox_create_contact    (mrmailbox_t*, const char* name, const char* addr);
 int             mrmailbox_add_address_book  (mrmailbox_t*, const char*);
-mrarray_t*      mrmailbox_get_known_contacts (mrmailbox_t*, const char* query);
+
+#define         MR_GCL_VERIFIED_ONLY 0x01
+#define         MR_GCL_ADD_SELF      0x02
+mrarray_t*      mrmailbox_get_contacts      (mrmailbox_t*, uint32_t flags, const char* query);
+
 int             mrmailbox_get_blocked_count (mrmailbox_t*);
 mrarray_t*      mrmailbox_get_blocked_contacts (mrmailbox_t*);
 void            mrmailbox_block_contact     (mrmailbox_t*, uint32_t contact_id, int block);
@@ -303,16 +307,18 @@ void            mrmailbox_heartbeat         (mrmailbox_t*);
 
 
 /* out-of-band verification */
-#define         MR_QR_ASK_SECUREJOIN        200 /* id=contact */
+#define         MR_QR_ASK_VERIFYCONTACT     200 /* id=contact */
+#define         MR_QR_ASK_VERIFYGROUP       202 /* text1=groupname */
 #define         MR_QR_FPR_OK                210 /* id=contact */
 #define         MR_QR_FPR_MISMATCH          220 /* id=contact */
 #define         MR_QR_FPR_WITHOUT_ADDR      230 /* test1=formatted fingerprint */
 #define         MR_QR_ADDR                  320 /* id=contact */
 #define         MR_QR_TEXT                  330 /* text1=text */
+#define         MR_QR_URL                   332 /* text1=text */
 #define         MR_QR_ERROR                 400 /* text1=error string */
 mrlot_t*        mrmailbox_check_qr          (mrmailbox_t*, const char* qr);
 char*           mrmailbox_get_securejoin_qr (mrmailbox_t*, uint32_t chat_id);
-int             mrmailbox_join_securejoin   (mrmailbox_t*, const char* qr);
+uint32_t        mrmailbox_join_securejoin   (mrmailbox_t*, const char* qr);
 
 
 /* deprecated functions */

+ 102 - 49
deltachat-ios/libraries/deltachat-core/src/mrmailbox_e2ee.c

@@ -320,6 +320,7 @@ cleanup:
 void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
                     int force_unencrypted,
                     int e2ee_guaranteed, /*set if e2ee was possible on sending time; we should not degrade to transport*/
+                    int min_verified,
                     struct mailmime* in_out_message, mrmailbox_e2ee_helper_t* helper)
 {
 	int                    locked = 0, col = 0, do_encrypt = 0;
@@ -366,11 +367,12 @@ void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
 			for( iter1 = clist_begin(recipients_addr); iter1!=NULL ; iter1=clist_next(iter1) ) {
 				const char* recipient_addr = clist_content(iter1);
 				mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
+				mrkey_t* key_to_use = NULL;
 				if( mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, recipient_addr)
-				 && mrapeerstate_peek_key(peerstate)
+				 && (key_to_use=mrapeerstate_peek_key(peerstate, min_verified)) != NULL
 				 && (peerstate->m_prefer_encrypt==MRA_PE_MUTUAL || e2ee_guaranteed) )
 				{
-					mrkeyring_add(keyring, mrapeerstate_peek_key(peerstate)); /* we always add all recipients (even on IMAP upload) as otherwise forwarding may fail */
+					mrkeyring_add(keyring, key_to_use); /* we always add all recipients (even on IMAP upload) as otherwise forwarding may fail */
 					mrarray_add_ptr(peerstates, peerstate);
 				}
 				else {
@@ -415,7 +417,7 @@ void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
 		int iCnt = mrarray_get_cnt(peerstates);
 		if( iCnt > 1 ) {
 			for( int i = 0; i < iCnt; i++ ) {
-				char* p = mrapeerstate_render_gossip_header((mrapeerstate_t*)mrarray_get_ptr(peerstates, i));
+				char* p = mrapeerstate_render_gossip_header((mrapeerstate_t*)mrarray_get_ptr(peerstates, i), min_verified);
 				if( p ) {
 					mailimf_fields_add(imffields_encrypted, mailimf_field_new_custom(strdup("Autocrypt-Gossip"), p/*takes ownership*/));
 				}
@@ -436,7 +438,7 @@ void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
 					struct mailimf_optional_field* opt_field = field->fld_data.fld_optional_field;
 					if( opt_field && opt_field->fld_name ) {
 						if(  strncmp(opt_field->fld_name, "Secure-Join", 11)==0
-						 || (strncmp(opt_field->fld_name, "Chat-", 5)==0 && strcmp(opt_field->fld_name, "Chat-Version")!=0)/*Chat-Version may be used for filtering, however, this is subject to cha*/ ) {
+						 || (strncmp(opt_field->fld_name, "Chat-", 5)==0 && strcmp(opt_field->fld_name, "Chat-Version")!=0)/*Chat-Version may be used for filtering and is not added to the encrypted part, however, this is subject to change*/ ) {
 							move_to_encrypted = 1;
 						}
 					}
@@ -520,6 +522,20 @@ void mrmailbox_e2ee_thanks(mrmailbox_e2ee_helper_t* helper)
 
 	free(helper->m_cdata_to_free);
 	helper->m_cdata_to_free = NULL;
+
+	if( helper->m_gossipped_addr )
+	{
+		mrhash_clear(helper->m_gossipped_addr);
+		free(helper->m_gossipped_addr);
+		helper->m_gossipped_addr = NULL;
+	}
+
+	if( helper->m_signatures )
+	{
+		mrhash_clear(helper->m_signatures);
+		free(helper->m_signatures);
+		helper->m_signatures = NULL;
+	}
 }
 
 
@@ -548,8 +564,8 @@ static int has_decrypted_pgp_armor(const char* str__, int str_bytes)
 static int decrypt_part(mrmailbox_t*       mailbox,
                         struct mailmime*   mime,
                         const mrkeyring_t* private_keyring,
-                        const mrkey_t*     public_key_for_validate, /*may be NULL*/
-                        int*               ret_validation_errors,
+                        const mrkeyring_t* public_keyring_for_validate, /*may be NULL*/
+                        mrhash_t*          ret_valid_signatures,
                         struct mailmime**  ret_decrypted_mime)
 {
 	struct mailmime_data*        mime_data;
@@ -559,7 +575,6 @@ static int decrypt_part(mrmailbox_t*       mailbox,
 	size_t                       decoded_data_bytes = 0;
 	void*                        plain_buf = NULL;
 	size_t                       plain_bytes = 0;
-	int                          part_validation_errors = 0;
 	int                          sth_decrypted = 0;
 
 	*ret_decrypted_mime = NULL;
@@ -614,15 +629,14 @@ static int decrypt_part(mrmailbox_t*       mailbox,
 		goto cleanup;
 	}
 
-	if( !mrpgp_pk_decrypt(mailbox, decoded_data, decoded_data_bytes, private_keyring, public_key_for_validate, 1, &plain_buf, &plain_bytes, &part_validation_errors)
+	mrhash_t* add_signatures = mrhash_count(ret_valid_signatures)<=0?
+		ret_valid_signatures : NULL; /*if we already have fingerprints, do not add more; this ensures, only the fingerprints from the outer-most part are collected */
+
+	if( !mrpgp_pk_decrypt(mailbox, decoded_data, decoded_data_bytes, private_keyring, public_keyring_for_validate, 1, &plain_buf, &plain_bytes, add_signatures)
 	 || plain_buf==NULL || plain_bytes<=0 ) {
 		goto cleanup;
 	}
 
-	if( part_validation_errors ) {
-		(*ret_validation_errors) |= part_validation_errors;
-	}
-
 	//{char* t1=mr_null_terminate(plain_buf,plain_bytes);printf("\n**********\n%s\n**********\n",t1);free(t1);}
 
 	{
@@ -634,7 +648,7 @@ static int decrypt_part(mrmailbox_t*       mailbox,
 			goto cleanup;
 		}
 
-		//mailmime_print(new_mime);
+		//mailmime_print(decrypted_mime);
 
 		*ret_decrypted_mime = decrypted_mime;
 		sth_decrypted = 1;
@@ -654,9 +668,10 @@ cleanup:
 static int decrypt_recursive(mrmailbox_t*            mailbox,
                              struct mailmime*        mime,
                              const mrkeyring_t*      private_keyring,
-                             const mrkey_t*          public_key_for_validate,
-                             int*                    ret_validation_errors,
-                             struct mailimf_fields** ret_gossip_headers )
+                             const mrkeyring_t*      public_keyring_for_validate,
+                             mrhash_t*               ret_valid_signatures,
+                             struct mailimf_fields** ret_gossip_headers,
+                             int*                    ret_has_unencrypted_parts )
 {
 	struct mailmime_content* ct;
 	clistiter*               cur;
@@ -673,11 +688,11 @@ static int decrypt_recursive(mrmailbox_t*            mailbox,
 			"application/octet-stream" (the interesting data part) and optional, unencrypted help files */
 			for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
 				struct mailmime* decrypted_mime = NULL;
-				if( decrypt_part(mailbox, (struct mailmime*)clist_content(cur), private_keyring, public_key_for_validate, ret_validation_errors, &decrypted_mime) )
+				if( decrypt_part(mailbox, (struct mailmime*)clist_content(cur), private_keyring, public_keyring_for_validate, ret_valid_signatures, &decrypted_mime) )
 				{
 					/* remember the header containing potentially Autocrypt-Gossip */
 					if( *ret_gossip_headers == NULL /* use the outermost decrypted part */
-					 && (*ret_validation_errors) == 0 /* do not trust the gossipped keys when the message cannot be validated eg. due to a bad signature */ )
+					 && mrhash_count(ret_valid_signatures) > 0 /* do not trust the gossipped keys when the message cannot be validated eg. due to a bad signature */ )
 					{
 						size_t dummy = 0;
 						struct mailimf_fields* test = NULL;
@@ -693,10 +708,11 @@ static int decrypt_recursive(mrmailbox_t*            mailbox,
 					return 1; /* sth. decrypted, start over from root searching for encrypted parts */
 				}
 			}
+			*ret_has_unencrypted_parts = 1; // there is a part that could not be decrypted
 		}
 		else {
 			for( cur=clist_begin(mime->mm_data.mm_multipart.mm_mp_list); cur!=NULL; cur=clist_next(cur)) {
-				if( decrypt_recursive(mailbox, (struct mailmime*)clist_content(cur), private_keyring, public_key_for_validate, ret_validation_errors, ret_gossip_headers) ) {
+				if( decrypt_recursive(mailbox, (struct mailmime*)clist_content(cur), private_keyring, public_keyring_for_validate, ret_valid_signatures, ret_gossip_headers, ret_has_unencrypted_parts) ) {
 					return 1; /* sth. decrypted, start over from root searching for encrypted parts */
 				}
 			}
@@ -704,19 +720,24 @@ static int decrypt_recursive(mrmailbox_t*            mailbox,
 	}
 	else if( mime->mm_type == MAILMIME_MESSAGE )
 	{
-		if( decrypt_recursive(mailbox, mime->mm_data.mm_message.mm_msg_mime, private_keyring, public_key_for_validate, ret_validation_errors, ret_gossip_headers) ) {
+		if( decrypt_recursive(mailbox, mime->mm_data.mm_message.mm_msg_mime, private_keyring, public_keyring_for_validate, ret_valid_signatures, ret_gossip_headers, ret_has_unencrypted_parts) ) {
 			return 1; /* sth. decrypted, start over from root searching for encrypted parts */
 		}
 	}
+	else
+	{
+		*ret_has_unencrypted_parts = 1; // there is a part that was not encrypted at all. in combination with otherwise encrypted mails, this is a problem.
+	}
 
 	return 0;
 }
 
 
-static void update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time, struct mailimf_fields* imffields, const struct mailimf_fields* gossip_headers)
+static mrhash_t* update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time, struct mailimf_fields* imffields, const struct mailimf_fields* gossip_headers)
 {
 	clistiter* cur1;
 	mrhash_t*  recipients = NULL;
+	mrhash_t*  gossipped_addr = NULL;
 
 	for( cur1 = clist_begin(gossip_headers->fld_list); cur1!=NULL ; cur1=clist_next(cur1) )
 	{
@@ -739,15 +760,30 @@ static void update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time,
 					{
 						/* valid recipient: update peerstate */
 						mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
-						if( !mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, gossip_header->m_addr) ) {
-							mrapeerstate_init_from_gossip(peerstate, gossip_header, message_time);
-							mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 1/*create*/);
-						}
-						else {
-							mrapeerstate_apply_gossip(peerstate, gossip_header, message_time);
-							mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 0/*do not create*/);
+						mrsqlite3_lock(mailbox->m_sql);
+							if( !mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, gossip_header->m_addr) ) {
+								mrapeerstate_init_from_gossip(peerstate, gossip_header, message_time);
+								mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 1/*create*/);
+							}
+							else {
+								mrapeerstate_apply_gossip(peerstate, gossip_header, message_time);
+								mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 0/*do not create*/);
+							}
+						mrsqlite3_unlock(mailbox->m_sql);
+
+						if( peerstate->m_degrade_event ) {
+							mrmailbox_handle_degrade_event(mailbox, peerstate);
 						}
+
 						mrapeerstate_unref(peerstate);
+
+						// collect all gossipped addresses; we need them later to mark them as being
+						// verified when used in a verified group by a verified sender
+						if( gossipped_addr == NULL ) {
+							gossipped_addr = malloc(sizeof(mrhash_t));
+							mrhash_init(gossipped_addr, MRHASH_STRING, 1/*copy key*/);
+						}
+						mrhash_insert(gossipped_addr, gossip_header->m_addr, strlen(gossip_header->m_addr), (void*)1);
 					}
 					else
 					{
@@ -763,10 +799,13 @@ static void update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time,
 		mrhash_clear(recipients);
 		free(recipients);
 	}
+
+	return gossipped_addr;
 }
 
 
-int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message, int* ret_validation_errors, int* ret_degrade_event)
+void mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message,
+                           mrmailbox_e2ee_helper_t* helper)
 {
 	/* return values: 0=nothing to decrypt/cannot decrypt, 1=sth. decrypted
 	(to detect parts that could not be decrypted, simply look for left "multipart/encrypted" MIME types */
@@ -777,15 +816,13 @@ int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message
 	int                    locked = 0;
 	char*                  from = NULL, *self_addr = NULL;
 	mrkeyring_t*           private_keyring = mrkeyring_new();
-	int                    sth_decrypted = 0;
+	mrkeyring_t*           public_keyring_for_validate = mrkeyring_new();
 	struct mailimf_fields* gossip_headers = NULL;
 
-	if( ret_degrade_event ) {
-		*ret_degrade_event = 0;
-	}
+	if( helper ) { memset(helper, 0, sizeof(mrmailbox_e2ee_helper_t)); }
 
-	if( mailbox==NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || in_out_message==NULL || ret_validation_errors==NULL
-	 || imffields==NULL || peerstate==NULL || private_keyring==NULL ) {
+	if( mailbox==NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || in_out_message==NULL
+	 || helper == NULL || imffields==NULL ) {
 		goto cleanup;
 	}
 
@@ -847,10 +884,6 @@ int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message
 			}
 		}
 
-		if( ret_degrade_event ) {
-			*ret_degrade_event = peerstate->m_degrade_event;
-		}
-
 		/* load private key for decryption */
 		if( (self_addr=mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", NULL))==NULL ) {
 			goto cleanup;
@@ -868,22 +901,42 @@ int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message
 	mrsqlite3_unlock(mailbox->m_sql);
 	locked = 0;
 
+	if( peerstate->m_degrade_event ) {
+		mrmailbox_handle_degrade_event(mailbox, peerstate);
+	}
+
+	// offer both, gossip and public, for signature validation.
+	// the caller may check the signature fingerprints as needed later.
+	mrkeyring_add(public_keyring_for_validate, peerstate->m_gossip_key);
+	mrkeyring_add(public_keyring_for_validate, peerstate->m_public_key);
+
 	/* finally, decrypt.  If sth. was decrypted, decrypt_recursive() returns "true" and we start over to decrypt maybe just added parts. */
-	*ret_validation_errors = 0;
-	int avoid_deadlock = 10;
-	while( avoid_deadlock > 0 ) {
+	helper->m_signatures = malloc(sizeof(mrhash_t));
+	mrhash_init(helper->m_signatures, MRHASH_STRING, 1/*copy key*/);
+
+	int iterations = 0;
+	while( iterations < 10 ) {
+		int has_unencrypted_parts = 0;
 		if( !decrypt_recursive(mailbox, in_out_message, private_keyring,
-		        peerstate->m_public_key, /* never use gossip_key for validation - if we get a mail to validate from the user, we normally also have the public_key */
-		        ret_validation_errors, &gossip_headers) ) {
+		        public_keyring_for_validate,
+		        helper->m_signatures, &gossip_headers, &has_unencrypted_parts) ) {
 			break;
 		}
-		sth_decrypted = 1;
-		avoid_deadlock--;
+
+		// if we're here, sth. was encrypted. if we're on top-level, and there are no
+		// additional unencrypted parts in the message the encryption was fine
+		// (signature is handled separately and returned as m_signatures)
+		if( iterations == 0
+		 && !has_unencrypted_parts ) {
+			helper->m_encrypted = 1;
+		}
+
+		iterations++;
 	}
 
-	/* check for Autocrypt-Gossip (NB: maybe we should use this header also for mrmimeparser_t::m_header_protected)  */
+	/* check for Autocrypt-Gossip */
 	if( gossip_headers ) {
-		update_gossip_peerstates(mailbox, message_time, imffields, gossip_headers);
+		helper->m_gossipped_addr = update_gossip_peerstates(mailbox, message_time, imffields, gossip_headers);
 	}
 
 	//mailmime_print(in_out_message);
@@ -894,8 +947,8 @@ cleanup:
 	mraheader_unref(autocryptheader);
 	mrapeerstate_unref(peerstate);
 	mrkeyring_unref(private_keyring);
+	mrkeyring_unref(public_keyring_for_validate);
 	free(from);
 	free(self_addr);
-	return sth_decrypted;
 }
 

+ 1 - 1
deltachat-ios/libraries/deltachat-core/src/mrmailbox_imex.c

@@ -511,7 +511,7 @@ char* mrmailbox_initiate_key_transfer(mrmailbox_t* mailbox)
 	mrparam_set    (msg->m_param, MRP_FILE,              setup_file_name);
 	mrparam_set    (msg->m_param, MRP_MIMETYPE,          "application/autocrypt-setup");
 	mrparam_set_int(msg->m_param, MRP_CMD,               MR_CMD_AUTOCRYPT_SETUP_MESSAGE);
-	mrparam_set_int(msg->m_param, MRP_FORCE_UNENCRYPTED, 2); // 2=do not even add Autocrypt-header
+	mrparam_set_int(msg->m_param, MRP_FORCE_PLAINTEXT,   2); // 2=do not even add Autocrypt-header
 
 	CHECK_EXIT
 

+ 3 - 2
deltachat-ios/libraries/deltachat-core/src/mrmailbox_keyhistory.c

@@ -1,7 +1,7 @@
 /*******************************************************************************
  *
  *                              Delta Chat Core
- *                      Copyright (C) 2018 Björn Petersen
+ *                      Copyright (C) 2017 Björn Petersen
  *                   Contact: r10s@b44t.com, http://b44t.com
  *
  * This program is free software: you can redistribute it and/or modify it under
@@ -26,4 +26,5 @@
 void mrmailbox_add_to_keyhistory__(mrmailbox_t* mailbox, const char* rfc724_mid, time_t sending_time, const char* addr, const char* fingerprint)
 {
 	// TODO
-}
+}
+

+ 48 - 18
deltachat-ios/libraries/deltachat-core/src/mrmailbox_qr.c

@@ -51,12 +51,14 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
 	char*           addr          = NULL; /* must be normalized, if set */
 	char*           fingerprint   = NULL; /* must be normalized, if set */
 	char*           name          = NULL;
-	char*           random_public = NULL;
-	char*           random_secret = NULL;
+	char*           invitenumber  = NULL;
+	char*           auth          = NULL;
 	mrapeerstate_t* peerstate     = mrapeerstate_new(mailbox);
 	mrlot_t*        qr_parsed     = mrlot_new();
 	uint32_t        chat_id       = 0;
 	char*           device_msg    = NULL;
+	char*           grpid         = NULL;
+	char*           grpname       = NULL;
 
 	qr_parsed->m_state = 0;
 
@@ -71,7 +73,9 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
 
 	if( strncasecmp(qr, OPENPGP4FPR_SCHEME, strlen(OPENPGP4FPR_SCHEME)) == 0 )
 	{
-		/* scheme: OPENPGP4FPR:1234567890123456789012345678901234567890#v=mail%40domain.de&n=Name */
+		/* scheme: OPENPGP4FPR:FINGERPRINT#a=ADDR&n=NAME&i=INVITENUMBER&s=AUTH
+		       or: OPENPGP4FPR:FINGERPRINT#a=ADDR&g=GROUPNAME&x=GROUPID&i=INVITENUMBER&s=AUTH */
+
 		payload  = safe_strdup(&qr[strlen(OPENPGP4FPR_SCHEME)]);
 		char* fragment = strchr(payload, '#'); /* must not be freed, only a pointer inside payload */
 		if( fragment )
@@ -82,16 +86,26 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
 			mrparam_t* param = mrparam_new();
 			mrparam_set_urlencoded(param, fragment);
 
-			addr = mrparam_get(param, 'v', NULL);
+			addr = mrparam_get(param, 'a', NULL);
 			if( addr ) {
-				char* name_urlencoded = mrparam_get(param, 'n', NULL);
-				if( name_urlencoded ) {
-					name = mr_url_decode(name_urlencoded);
+				char* urlencoded = mrparam_get(param, 'n', NULL);
+				if(urlencoded ) {
+					name = mr_url_decode(urlencoded);
 					mr_normalize_name(name);
-					free(name_urlencoded);
+					free(urlencoded);
+				}
+
+				invitenumber  = mrparam_get(param, 'i', NULL);
+				auth          = mrparam_get(param, 's', NULL);
+
+				grpid  = mrparam_get(param, 'x', NULL);
+				if( grpid ) {
+					urlencoded = mrparam_get(param, 'g', NULL);
+					if( urlencoded ) {
+						grpname = mr_url_decode(urlencoded);
+						free(urlencoded);
+					}
 				}
-				random_public = mrparam_get(param, 'p', "");
-				random_secret = mrparam_get(param, 's', "");
 			}
 
 			mrparam_unref(param);
@@ -189,7 +203,7 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
 	{
 		/* fingerprint set ... */
 
-		if( addr == NULL || random_public == NULL || random_secret == NULL )
+		if( addr == NULL || invitenumber == NULL || auth == NULL )
 		{
 			// _only_ fingerprint set ...
 			// (we could also do this before/instead of a secure-join, however, this may require complicated questions in the ui)
@@ -214,16 +228,25 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
 		else
 		{
 			// fingerprint + addr set, secure-join requested
-			// do not comapre the fingerprint already, errors are catched later more proberly.
-			// (theroretically, there is also the state "addr=set, fingerprint=set, random_public=0", however, currently, we won't get into this state)
+			// do not comapre the fingerprint already - it may have changed - errors are catched later more proberly.
+			// (theroretically, there is also the state "addr=set, fingerprint=set, invitenumber=0", however, currently, we won't get into this state)
 			mrsqlite3_lock(mailbox->m_sql);
 			locked = 1;
 
-				qr_parsed->m_state         = MR_QR_ASK_SECUREJOIN;
+				if( grpid && grpname ) {
+					qr_parsed->m_state = MR_QR_ASK_VERIFYGROUP;
+					qr_parsed->m_text1 = safe_strdup(grpname);
+					qr_parsed->m_text2 = safe_strdup(grpid);
+				}
+				else {
+					qr_parsed->m_state = MR_QR_ASK_VERIFYCONTACT;
+				}
+
 				qr_parsed->m_id            = mrmailbox_add_or_lookup_contact__(mailbox, name, addr, MR_ORIGIN_UNHANDLED_QR_SCAN, NULL);
 				qr_parsed->m_fingerprint   = safe_strdup(fingerprint);
-				qr_parsed->m_random_public = safe_strdup(random_public);
-				qr_parsed->m_random_secret = safe_strdup(random_secret);
+				qr_parsed->m_invitenumber  = safe_strdup(invitenumber);
+				qr_parsed->m_auth          = safe_strdup(auth);
+
 
 			mrsqlite3_unlock(mailbox->m_sql);
 			locked = 0;
@@ -234,6 +257,11 @@ mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
         qr_parsed->m_state = MR_QR_ADDR;
 		qr_parsed->m_id    = mrmailbox_add_or_lookup_contact__(mailbox, name, addr, MR_ORIGIN_UNHANDLED_QR_SCAN, NULL);
 	}
+	else if( strstr(qr, "http://")==qr || strstr(qr, "https://")==qr )
+	{
+		qr_parsed->m_state = MR_QR_URL;
+		qr_parsed->m_text1 = safe_strdup(qr);
+	}
 	else
 	{
         qr_parsed->m_state = MR_QR_TEXT;
@@ -251,9 +279,11 @@ cleanup:
 	mrapeerstate_unref(peerstate);
 	free(payload);
 	free(name);
-	free(random_public);
-	free(random_secret);
+	free(invitenumber);
+	free(auth);
 	free(device_msg);
+	free(grpname);
+	free(grpid);
 	return qr_parsed;
 }
 

+ 161 - 96
deltachat-ios/libraries/deltachat-core/src/mrmailbox_receive_imf.c

@@ -20,9 +20,11 @@
  ******************************************************************************/
 
 
+#include <assert.h>
 #include "mrmailbox_internal.h"
 #include "mrmimeparser.h"
 #include "mrmimefactory.h"
+#include "mrapeerstate.h"
 #include "mrimap.h"
 #include "mrjob.h"
 #include "mrarray-private.h"
@@ -75,7 +77,7 @@ static void add_or_lookup_contact_by_addr__(mrmailbox_t* mailbox, const char* di
 }
 
 
-static void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* mailbox, struct mailimf_mailbox_list* mb_list, int origin, mrarray_t* ids, int* check_self)
+static void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* mailbox, const struct mailimf_mailbox_list* mb_list, int origin, mrarray_t* ids, int* check_self)
 {
 	clistiter* cur;
 
@@ -92,7 +94,7 @@ static void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* mail
 }
 
 
-static void mrmailbox_add_or_lookup_contacts_by_address_list__(mrmailbox_t* mailbox, struct mailimf_address_list* adr_list, int origin, mrarray_t* ids, int* check_self)
+static void mrmailbox_add_or_lookup_contacts_by_address_list__(mrmailbox_t* mailbox, const struct mailimf_address_list* adr_list, int origin, mrarray_t* ids, int* check_self)
 {
 	clistiter* cur;
 
@@ -276,18 +278,7 @@ static int mrmailbox_is_reply_to_messenger_message__(mrmailbox_t* mailbox, mrmim
  ******************************************************************************/
 
 
-static uint32_t add_degrade_message__(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t from_id, time_t timestamp)
-{
-	mrcontact_t* contact = mrcontact_new(mailbox);
-	mrcontact_load_from_db__(contact, mailbox->m_sql, from_id);
-
-	char* msg = mr_mprintf("Changed setup for %s", contact->m_addr);
-	uint32_t msg_id = mrmailbox_add_device_msg__(mailbox, chat_id, msg, timestamp);
-	free(msg);
 
-	mrcontact_unref(contact);
-	return msg_id;
-}
 
 
 static void mrmailbox_calc_timestamps__(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t from_id, time_t message_timestamp, int is_fresh_msg,
@@ -371,7 +362,7 @@ static mrarray_t* search_chat_ids_by_contact_ids(mrmailbox_t* mailbox, const mra
 	                     " FROM chats_contacts cc "
 	                     " LEFT JOIN chats c ON c.id=cc.chat_id "
 	                     " WHERE cc.chat_id IN(SELECT chat_id FROM chats_contacts WHERE contact_id IN(%s))"
-	                     "   AND c.type=" MR_STRINGIFY(MR_CHAT_TYPE_GROUP) /* do not select normal chats which are equal to a group with a single member and without SELF */
+	                     "   AND c.type=" MR_STRINGIFY(MR_CHAT_TYPE_GROUP) /* no verified groups and no single chats (which are equal to a group with a single member and without SELF) */
 	                     "   AND cc.contact_id!=" MR_STRINGIFY(MR_CONTACT_ID_SELF) /* ignore SELF, we've also removed it above - if the user has left the group, it is still the same group */
 	                     " ORDER BY cc.chat_id, cc.contact_id;",
 	                     contact_ids_str);
@@ -482,14 +473,14 @@ static char* create_adhoc_grp_id__(mrmailbox_t* mailbox, mrarray_t* member_ids /
 }
 
 
-static uint32_t create_group_record__(mrmailbox_t* mailbox, const char* grpid, const char* grpname, int create_blocked)
+static uint32_t create_group_record__(mrmailbox_t* mailbox, const char* grpid, const char* grpname, int create_blocked, int create_verified)
 {
 	uint32_t      chat_id = 0;
 	sqlite3_stmt* stmt = NULL;
 
 	stmt = mrsqlite3_prepare_v2_(mailbox->m_sql,
 		"INSERT INTO chats (type, name, grpid, blocked) VALUES(?, ?, ?, ?);");
-	sqlite3_bind_int (stmt, 1, MR_CHAT_TYPE_GROUP);
+	sqlite3_bind_int (stmt, 1, create_verified? MR_CHAT_TYPE_VERIFIED_GROUP : MR_CHAT_TYPE_GROUP);
 	sqlite3_bind_text(stmt, 2, grpname, -1, SQLITE_STATIC);
 	sqlite3_bind_text(stmt, 3, grpid, -1, SQLITE_STATIC);
 	sqlite3_bind_int (stmt, 4, create_blocked);
@@ -572,10 +563,10 @@ static void create_or_lookup_adhoc_group__(mrmailbox_t* mailbox, mrmimeparser_t*
 	}
 
 	/* create group record */
-	chat_id = create_group_record__(mailbox, grpid, grpname, create_blocked);
+	chat_id = create_group_record__(mailbox, grpid, grpname, create_blocked, 0);
 	chat_id_blocked = create_blocked;
 	for( i = 0; i < mrarray_get_cnt(member_ids); i++ ) {
-		mrmailbox_add_contact_to_chat__(mailbox, chat_id, mrarray_get_id(member_ids, i));
+		mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, mrarray_get_id(member_ids, i));
 	}
 
 	mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0);
@@ -593,6 +584,84 @@ cleanup:
 }
 
 
+static int check_verified_properties__(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser,
+                                       uint32_t from_id, const mrarray_t* to_ids)
+{
+	int             everythings_okay = 0;
+	mrcontact_t*    contact          = mrcontact_new(mailbox);
+	mrapeerstate_t* peerstate        = mrapeerstate_new(mailbox);
+	char*           to_ids_str       = NULL;
+	char*           q3               = NULL;
+	sqlite3_stmt*   stmt             = NULL;
+
+	// ensure, the contact is verified
+	if( !mrcontact_load_from_db__(contact, mailbox->m_sql, from_id)
+	 || !mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, contact->m_addr)
+	 || mrcontact_is_verified__(contact, peerstate) < MRV_BIDIRECTIONAL ) {
+		mrmailbox_log_warning(mailbox, 0, "Cannot verifiy group; sender is not verified.");
+		goto cleanup;
+	}
+
+	// ensure, the message is encrypted
+	if( !mimeparser->m_e2ee_helper->m_encrypted ) {
+		mrmailbox_log_warning(mailbox, 0, "Cannot verifiy group; message is not encrypted properly.");
+		goto cleanup;
+	}
+
+	// ensure, the message is signed with a verified key of the sender
+	if( !mrapeerstate_has_verified_key(peerstate, mimeparser->m_e2ee_helper->m_signatures) ) {
+		mrmailbox_log_warning(mailbox, 0, "Cannot verifiy group; message is not signed properly.");
+		goto cleanup;
+    }
+
+	// check that all members are verified.
+	// if a verification is missing, check if this was just gossiped - as we've verified the sender, we verify the member then.
+	to_ids_str = mrarray_get_string(to_ids, ",");
+	q3 = sqlite3_mprintf("SELECT c.addr, ps.public_key_verified, ps.gossip_key_verified "
+						 " FROM contacts c "
+						 " LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
+						 " WHERE c.id IN(%s) ",
+						 to_ids_str);
+	stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, q3);
+	if( sqlite3_step(stmt)==SQLITE_ROW )
+	{
+		const char* to_addr     = (const char*)sqlite3_column_text(stmt, 0);
+		int public_key_verified =              sqlite3_column_int (stmt, 1);
+		int gossip_key_verified =              sqlite3_column_int (stmt, 2);
+
+		if( gossip_key_verified < MRV_BIDIRECTIONAL
+		 && mrhash_find_str(mimeparser->m_e2ee_helper->m_gossipped_addr, to_addr)
+		 && mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, to_addr) )
+		{
+			// mark gossip-key as verified even if there is a public-verified-key; mrapeerstate_peek_key() will peek the newer one
+			// (the date is already updated in update_gossip_peerstates())
+			mrmailbox_log_info(mailbox, 0, "Marking gossipped key %s as verified due to verified %s.", to_addr, contact->m_addr);
+			mrapeerstate_set_verified(peerstate, MRA_GOSSIP_KEY, peerstate->m_gossip_key_fingerprint, MRV_BIDIRECTIONAL);
+			mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 0);
+			gossip_key_verified = MRV_BIDIRECTIONAL;
+		}
+
+		if( public_key_verified < MRV_BIDIRECTIONAL && gossip_key_verified < MRV_BIDIRECTIONAL )
+		{
+			mrmailbox_log_warning(mailbox, 0, "Cannot verifiy group; recipient %s is not gossipped.", to_addr);
+			goto cleanup;
+		}
+	}
+
+	// it's up to the caller to check if the sender is a member of the group
+	// (we do this for both, verified and unverified group, so we do not check this here)
+	everythings_okay = 1;
+
+cleanup:
+	if( stmt ) { sqlite3_finalize(stmt); }
+	mrcontact_unref(contact);
+	mrapeerstate_unref(peerstate);
+	free(to_ids_str);
+	if( q3 ) { sqlite3_free(q3); }
+	return everythings_okay;
+}
+
+
 /* the function tries extracts the group-id from the message and returns the
 corresponding chat_id.  If the chat_id is not existant, it is created.
 If the message contains groups commands (name, profile image, changed members),
@@ -609,10 +678,11 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
                                      int32_t from_id, const mrarray_t* to_ids,
                                      uint32_t* ret_chat_id, int* ret_chat_id_blocked)
 {
-	uint32_t              chat_id = 0;
-	int                   chat_id_blocked = 0;
-	char*                 grpid = NULL;
-	char*                 grpname = NULL;
+	uint32_t              chat_id          = 0;
+	int                   chat_id_blocked  = 0;
+	int                   chat_id_verified = 0;
+	char*                 grpid            = NULL;
+	char*                 grpname          = NULL;
 	sqlite3_stmt*         stmt;
 	int                   i, to_ids_cnt = mrarray_get_cnt(to_ids);
 	char*                 self_addr = NULL;
@@ -692,12 +762,17 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
 	}
 
 	/* check, if we have a chat with this group ID */
-	stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_CHATS_WHERE_grpid,
-		"SELECT id, blocked FROM chats WHERE grpid=?;");
-	sqlite3_bind_text (stmt, 1, grpid, -1, SQLITE_STATIC);
-	if( sqlite3_step(stmt)==SQLITE_ROW ) {
-		chat_id = sqlite3_column_int(stmt, 0);
-		chat_id_blocked = sqlite3_column_int(stmt, 1);
+	if( (chat_id=mrmailbox_get_chat_id_by_grpid__(mailbox, grpid, &chat_id_blocked, &chat_id_verified))!=0 ) {
+		if( chat_id_verified
+		 && !check_verified_properties__(mailbox, mime_parser, from_id, to_ids) ) {
+			chat_id          = 0; // force the creation of an unverified ad-hoc group.
+			chat_id_blocked  = 0;
+			chat_id_verified = 0;
+			free(grpid);
+			grpid = NULL;
+			free(grpname);
+			grpname = NULL;
+		}
 	}
 
 	/* check if the sender is a member of the existing group -
@@ -714,13 +789,22 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
 	self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", "");
 	if( chat_id == 0
 	 && !mrmimeparser_is_mailinglist_message(mime_parser)
+	 && grpid
 	 && grpname
 	 && X_MrRemoveFromGrp==NULL /*otherwise, a pending "quit" message may pop up*/
 	 && (!group_explicitly_left || (X_MrAddToGrp&&strcasecmp(self_addr,X_MrAddToGrp)==0) ) /*re-create explicitly left groups only if ourself is re-added*/
 	 )
 	{
-		chat_id = create_group_record__(mailbox, grpid, grpname, create_blocked);
-		chat_id_blocked = create_blocked;
+		int create_verified = 0;
+		if( mrmimeparser_lookup_field(mime_parser, "Chat-Verified") ) {
+			if( check_verified_properties__(mailbox, mime_parser, from_id, to_ids) ) {
+				create_verified = 1;
+			}
+		}
+
+		chat_id = create_group_record__(mailbox, grpid, grpname, create_blocked, create_verified);
+		chat_id_blocked  = create_blocked;
+		chat_id_verified = create_verified;
 		recreate_member_list = 1;
 	}
 
@@ -795,13 +879,13 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
 		sqlite3_finalize(stmt);
 
 		if( skip==NULL || strcasecmp(self_addr, skip) != 0 ) {
-			mrmailbox_add_contact_to_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF);
+			mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, MR_CONTACT_ID_SELF);
 		}
 
 		if( from_id > MR_CONTACT_ID_LAST_SPECIAL ) {
 			if( mrmailbox_contact_addr_equals__(mailbox, from_id, self_addr)==0
 			 && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, from_id, skip)==0) ) {
-				mrmailbox_add_contact_to_chat__(mailbox, chat_id, from_id);
+				mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, from_id);
 			}
 		}
 
@@ -810,7 +894,7 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
 			uint32_t to_id = mrarray_get_id(to_ids, i); /* to_id is only once in to_ids and is non-special */
 			if( mrmailbox_contact_addr_equals__(mailbox, to_id, self_addr)==0
 			 && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, to_id, skip)==0) ) {
-				mrmailbox_add_contact_to_chat__(mailbox, chat_id, to_id);
+				mrmailbox_add_to_chat_contacts_table__(mailbox, chat_id, to_id);
 			}
 		}
 		send_EVENT_CHAT_MODIFIED = 1;
@@ -849,7 +933,7 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
                            const char* server_folder, uint32_t server_uid, uint32_t flags)
 {
 	/* the function returns the number of created messages in the database */
-	int              incoming = 0;
+	int              incoming = 1;
 	int              incoming_origin = 0;
 	#define          outgoing (!incoming)
 
@@ -881,12 +965,8 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 
 	carray*          rr_event_to_send = carray_new(16);
 
-	int              has_return_path = 0;
-	int              is_handshake_message = 0;
 	char*            txt_raw = NULL;
 
-	uint32_t         degrade_msg_id = 0;
-
 	mrmailbox_log_info(mailbox, 0, "Receiving message %s/%lu...", server_folder? server_folder:"?", server_uid);
 
 	to_ids = mrarray_new(mailbox, 16);
@@ -913,18 +993,21 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 		goto cleanup; /* Error - even adding an empty record won't help as we do not know the message ID */
 	}
 
-	/* Check, if the mail comes from extern, resp. is not sent by us.  This is a _really_ important step
-	as messages sent by us are used to validate other mail senders and receivers.
-	For this purpose, we assume, the `Return-Path:`-header is never present if the message is sent by us.
-	The `Received:`-header may be another idea, however, this is also set if mails are transfered from other accounts via IMAP.
-	Using `From:` alone is no good idea, as mailboxes may use different sending-addresses - moreover, they may change over the years.
-	However, we use `From:` as an additional hint below. */
-	if( mrmimeparser_lookup_field(mime_parser, "Return-Path") ) {
-		has_return_path = 1;
+	/* messages without a Return-Path header typically are outgoing, however, if the Return-Path header
+	is missing for other reasons, see issue #150, foreign messages appear as own messages, this is very confusing.
+	as it may even be confusing when _own_ messages sent from other devices with other e-mail-adresses appear as being sent from SELF
+	we disabled this check for now */
+	#if 0
+	if( !mrmimeparser_lookup_field(mime_parser, "Return-Path") ) {
+		incoming = 0;
 	}
+	#endif
 
-	if( has_return_path ) {
-		incoming = 1;
+	if( (field=mrmimeparser_lookup_field(mime_parser, "Date"))!=NULL && field->fld_type==MAILIMF_FIELD_ORIG_DATE ) {
+		struct mailimf_orig_date* orig_date = field->fld_data.fld_orig_date;
+		if( orig_date ) {
+			sent_timestamp = mr_timestamp_from_date(orig_date->dt_date_time); // is not yet checked against bad times! we do this later if we have the database information.
+		}
 	}
 
 	mrsqlite3_lock(mailbox->m_sql);
@@ -932,9 +1015,9 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 	mrsqlite3_begin_transaction__(mailbox->m_sql);
 	transaction_pending = 1;
 
-		/* for incoming messages, get From: and check if it is known (for known From:'s we add the other To:/Cc:/Bcc: in the 3rd pass) */
-		if( incoming
-		 && (field=mrmimeparser_lookup_field(mime_parser, "From"))!=NULL
+		/* get From: and check if it is known (for known From:'s we add the other To:/Cc: in the 3rd pass)
+		or if From: is equal to SELF (in this case, it is any outgoing messages, we do not check Return-Path any more as this is unreliable, see issue #150 */
+		if( (field=mrmimeparser_lookup_field(mime_parser, "From"))!=NULL
 		 && field->fld_type==MAILIMF_FIELD_FROM)
 		{
 			struct mailimf_from* fld_from = field->fld_data.fld_from;
@@ -945,16 +1028,12 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mailbox, fld_from->frm_mb_list, MR_ORIGIN_INCOMING_UNKNOWN_FROM, from_list, &check_self);
 				if( check_self )
 				{
+					incoming = 0;
+
 					if( mrmimeparser_sender_equals_recipient(mime_parser) )
 					{
 						from_id = MR_CONTACT_ID_SELF;
 					}
-					else
-					{
-						incoming = 0; /* The `Return-Path:`-approach above works well, however, there may be outgoing messages which we also receive -
-									  for these messages, the `Return-Path:` is set although we're the sender.  To correct these cases, we add an
-									  additional From: check - which, however, will not work for older From:-addresses used on the mailbox. */
-					}
 				}
 				else
 				{
@@ -968,7 +1047,7 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 			}
 		}
 
-		/* Make sure, to_ids starts with the first To:-address (Cc: and Bcc: are added in the loop below pass) */
+		/* Make sure, to_ids starts with the first To:-address (Cc: is added in the loop below pass) */
 		if( (field=mrmimeparser_lookup_field(mime_parser, "To"))!=NULL
 		 && field->fld_type==MAILIMF_FIELD_TO )
 		{
@@ -987,7 +1066,9 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 			 * Add parts
 			 *********************************************************************/
 
-			/* collect the rest information */
+			/* collect the rest information, CC: is added to the to-list, BCC: is ignored
+			(we should not add BCC to groups as this would split groups. We could add them as "known contacts",
+			however, the benefit is very small and this may leak data that is expected to be hidden) */
 			if( (field=mrmimeparser_lookup_field(mime_parser, "Cc"))!=NULL && field->fld_type==MAILIMF_FIELD_CC )
 			{
 				struct mailimf_cc* fld_cc = field->fld_data.fld_cc;
@@ -997,15 +1078,6 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				}
 			}
 
-			if( (field=mrmimeparser_lookup_field(mime_parser, "Bcc"))!=NULL && field->fld_type==MAILIMF_FIELD_BCC )
-			{
-				struct mailimf_bcc* fld_bcc = field->fld_data.fld_bcc;
-				if( outgoing && fld_bcc ) {
-					mrmailbox_add_or_lookup_contacts_by_address_list__(mailbox, fld_bcc->bcc_addr_list,
-						MR_ORIGIN_OUTGOING_BCC, to_ids, NULL);
-				}
-			}
-
 			/* check if the message introduces a new chat:
 			- outgoing messages introduce a chat with the first to: address if they are sent by a messenger
 			- incoming messages introduce a chat only for known contacts if they are sent by a messenger
@@ -1015,6 +1087,19 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				state = (flags&MR_IMAP_SEEN)? MR_STATE_IN_SEEN : MR_STATE_IN_FRESH;
 				to_id = MR_CONTACT_ID_SELF;
 
+				// handshake messages must be processed before chats are crated (eg. contacs may be marked as verified)
+				assert( chat_id == 0 );
+				if( mrmimeparser_lookup_field(mime_parser, "Secure-Join") ) {
+					mrsqlite3_commit__(mailbox->m_sql);
+					mrsqlite3_unlock(mailbox->m_sql);
+						if( mrmailbox_handle_securejoin_handshake(mailbox, mime_parser, from_id) == MR_IS_HANDSHAKE_STOP_NORMAL_PROCESSING ) {
+							hidden = 1;
+							state = MR_STATE_IN_SEEN;
+						}
+					mrsqlite3_lock(mailbox->m_sql);
+					mrsqlite3_begin_transaction__(mailbox->m_sql);
+				}
+
 				/* test if there is a normal chat with the sender - if so, this allows us to create groups in the next step */
 				uint32_t test_normal_chat_id = 0;
 				int      test_normal_chat_id_blocked = 0;
@@ -1126,23 +1211,8 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				}
 			}
 
-			/* check of the message is a special handshake message; if so, mark it as "seen" here and handle it when done */
-			is_handshake_message = mrmailbox_is_securejoin_handshake__(mailbox, mime_parser);
-			if( is_handshake_message ) {
-				hidden = 1;
-				if( state==MR_STATE_IN_FRESH || state==MR_STATE_IN_NOTICED ) {
-					state = MR_STATE_IN_SEEN;
-				}
-			}
-
 			/* correct message_timestamp, it should not be used before,
 			however, we cannot do this earlier as we need from_id to be set */
-			if( (field=mrmimeparser_lookup_field(mime_parser, "Date"))!=NULL && field->fld_type==MAILIMF_FIELD_ORIG_DATE ) {
-				struct mailimf_orig_date* orig_date = field->fld_data.fld_orig_date;
-				if( orig_date ) {
-					sent_timestamp = mr_timestamp_from_date(orig_date->dt_date_time); /* is not yet checked against bad times! */
-				}
-			}
 			mrmailbox_calc_timestamps__(mailbox, chat_id, from_id, sent_timestamp, (flags&MR_IMAP_SEEN)? 0 : 1 /*fresh message?*/,
 				&sort_timestamp, &sent_timestamp, &rcvd_timestamp);
 
@@ -1202,10 +1272,6 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				}
 			}
 
-			if( mime_parser->m_degrade_event ) {
-				degrade_msg_id = add_degrade_message__(mailbox, chat_id, from_id, sort_timestamp);
-			}
-
 			/* fine, so far.  now, split the message into simple parts usable as "short messages"
 			and add them to the database (mails sent by other messenger clients should result
 			into only one message; mails sent by other clients may result in several messages (eg. one per attachment)) */
@@ -1284,6 +1350,13 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				}
 			}
 		}
+		else
+		{
+			// there are no non-meta data in message, do some basic calculations so that the varaiables are correct in the further processing
+			if( sent_timestamp > time(NULL) ) {
+				sent_timestamp = time(NULL);
+			}
+		}
 
 
 		if( carray_count(mime_parser->m_reports) > 0 )
@@ -1339,7 +1412,7 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 											{
 												uint32_t chat_id = 0;
 												uint32_t msg_id = 0;
-												if( mrmailbox_mdn_from_ext__(mailbox, from_id, rfc724_mid, &chat_id, &msg_id) ) {
+												if( mrmailbox_mdn_from_ext__(mailbox, from_id, rfc724_mid, sent_timestamp, &chat_id, &msg_id) ) {
 													carray_add(rr_event_to_send, (void*)(uintptr_t)chat_id, NULL);
 													carray_add(rr_event_to_send, (void*)(uintptr_t)msg_id, NULL);
 												}
@@ -1395,14 +1468,6 @@ cleanup:
 	if( transaction_pending ) { mrsqlite3_rollback__(mailbox->m_sql); }
 	if( db_locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 
-	if( is_handshake_message ) {
-		mrmailbox_handle_securejoin_handshake(mailbox, mime_parser, chat_id); /* must be called after unlocking before deletion of mime_parser */
-	}
-	else if( mime_parser->m_degrade_event ) {
-		mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, degrade_msg_id);
-		mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0);
-	}
-
 	mrmimeparser_unref(mime_parser);
 	free(rfc724_mid);
 	mrarray_unref(to_ids);

+ 397 - 208
deltachat-ios/libraries/deltachat-core/src/mrmailbox_securejoin.c

@@ -29,70 +29,141 @@
 #include "mrmimefactory.h"
 #include "mrjob.h"
 
+#define      LOCK                 { mrsqlite3_lock  (mailbox->m_sql); locked = 1; }
+#define      UNLOCK  if( locked ) { mrsqlite3_unlock(mailbox->m_sql); locked = 0; }
+
 
 /*******************************************************************************
- * Tools: Alice's random_public and random_secret mini-datastore
+ * Tools: Alice's invitenumber and auth mini-datastore
  ******************************************************************************/
 
 
-/* the "mini-datastore is used to remember Alice's last few random_public and
-random_secret as they're written to a QR code.  This is needed for later
+/* the "mini-datastore is used to remember Alice's last few invitenumbers and
+auths as they're written to a QR code.  This is needed for later
 comparison when the data are provided by Bob. */
 
 
-static void store_random__(mrmailbox_t* mailbox, const char* datastore_name, const char* to_add)
+static void store_tag__(mrmailbox_t* mailbox, const char* datastore_name, const char* to_add)
 {
-	// prepend new random to the list of all tags
-	#define MAX_REMEMBERED_RANDOMS 10
-	#define MAX_REMEMBERED_CHARS (MAX_REMEMBERED_RANDOMS*(MR_CREATE_ID_LEN+1))
-	char* old_randoms = mrsqlite3_get_config__(mailbox->m_sql, datastore_name, "");
-	if( strlen(old_randoms) > MAX_REMEMBERED_CHARS ) {
-		old_randoms[MAX_REMEMBERED_CHARS] = 0; // the oldest tag may be incomplete und unrecognizable, however, this is no problem as it would be deleted soon anyway
-	}
-	char* new_randoms = mr_mprintf("%s,%s", to_add, old_randoms);
-	mrsqlite3_set_config__(mailbox->m_sql, datastore_name, new_randoms);
-
-	free(old_randoms);
-	free(new_randoms);
+	// prepend new tag to the list of all tags
+	#define MAX_REMEMBERED_TAGS 10
+	#define MAX_REMEMBERED_CHARS (MAX_REMEMBERED_TAGS*(MR_CREATE_ID_LEN+1))
+	char* old_tags = mrsqlite3_get_config__(mailbox->m_sql, datastore_name, "");
+	if( strlen(old_tags) > MAX_REMEMBERED_CHARS ) {
+		old_tags[MAX_REMEMBERED_CHARS] = 0; // the oldest tag may be incomplete and unrecognizable, however, this should not be a problem as it would be deleted soon anyway
+	}
+	char* new_tags = mr_mprintf("%s,%s", to_add, old_tags);
+	mrsqlite3_set_config__(mailbox->m_sql, datastore_name, new_tags);
+
+	free(old_tags);
+	free(new_tags);
 }
 
 
-static int lookup_random__(mrmailbox_t* mailbox, const char* datastore_name, const char* to_lookup)
+static int lookup_tag__(mrmailbox_t* mailbox, const char* datastore_name, const char* to_lookup)
 {
 	int            found       = 0;
-	char*          old_randoms = NULL;
+	char*          old_tags    = NULL;
 	carray*        lines       = NULL;
 
-	//mrstrbuilder_t new_randoms;  -- we do not delete the randoms to allow multiple scans, the randoms are deleted when new are generated
-	//mrstrbuilder_init(&new_randoms, 0);
-
-	old_randoms = mrsqlite3_get_config__(mailbox->m_sql, datastore_name, "");
-	mr_str_replace(&old_randoms, ",", "\n");
-	lines = mr_split_into_lines(old_randoms);
+	old_tags = mrsqlite3_get_config__(mailbox->m_sql, datastore_name, "");
+	mr_str_replace(&old_tags, ",", "\n");
+	lines = mr_split_into_lines(old_tags);
 	for( int i = 0; i < carray_count(lines); i++ ) {
-		char* random  = (char*)carray_get(lines, i); mr_trim(random);
-		if( strlen(random) >= 4 && strcmp(random, to_lookup) == 0 ) {
+		char* tag  = (char*)carray_get(lines, i); mr_trim(tag);
+		if( strlen(tag) >= 4 && strcmp(tag, to_lookup) == 0 ) {
 			found = 1;
 		}
-		//else {
-		//	mrstrbuilder_catf(&new_randoms, "%s,", random);
-		//}
 	}
 
-	//mrsqlite3_set_config__(mailbox->m_sql, datastore_name, new_randoms.m_buf);
-	//free(new_randoms.m_buf);
-
 	mr_free_splitted_lines(lines);
-	free(old_randoms);
+	free(old_tags);
 	return found;
 }
 
 
+/*******************************************************************************
+ * Tools: Handle degraded keys and lost verificaton
+ ******************************************************************************/
+
+
+void mrmailbox_handle_degrade_event(mrmailbox_t* mailbox, mrapeerstate_t* peerstate)
+{
+	sqlite3_stmt* stmt            = NULL;
+	int           locked          = 0;
+	uint32_t      contact_id      = 0;
+	uint32_t      contact_chat_id = 0;
+
+	if( mailbox == NULL || peerstate == NULL ) {
+		goto cleanup;
+	}
+
+	// - we do not issue an warning for MRA_DE_ENCRYPTION_PAUSED as this is quite normal
+	// - currently, we do not issue an extra warning for MRA_DE_VERIFICATION_LOST - this always comes
+	//   together with MRA_DE_FINGERPRINT_CHANGED which is logged, the idea is not to bother
+	//   with things they cannot fix, so the user is just kicked from the verified group
+	//   (and he will know this and can fix this)
+
+	if( peerstate->m_degrade_event & MRA_DE_FINGERPRINT_CHANGED )
+	{
+		LOCK
+
+			stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, "SELECT id FROM contacts WHERE addr=?;");
+				sqlite3_bind_text(stmt, 1, peerstate->m_addr, -1, SQLITE_STATIC);
+				sqlite3_step(stmt);
+				contact_id = sqlite3_column_int(stmt, 0);
+			sqlite3_finalize(stmt);
+
+			if( contact_id == 0 ) {
+				goto cleanup;
+			}
+
+			mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, contact_id, MR_CHAT_DEADDROP_BLOCKED, &contact_chat_id, NULL);
+
+		UNLOCK
+
+		char* msg = mr_mprintf("Changed setup for %s", peerstate->m_addr);
+		mrmailbox_add_device_msg(mailbox, contact_chat_id, msg);
+		free(msg);
+		mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, contact_chat_id, 0);
+	}
+
+cleanup:
+	UNLOCK
+}
+
+
 /*******************************************************************************
  * Tools: Misc.
  ******************************************************************************/
 
 
+static int encrypted_and_signed(mrmimeparser_t* mimeparser, const char* expected_fingerprint)
+{
+	if( !mimeparser->m_e2ee_helper->m_encrypted ) {
+		mrmailbox_log_warning(mimeparser->m_mailbox, 0, "Message not encrypted.");
+		return 0;
+	}
+
+	if( mrhash_count(mimeparser->m_e2ee_helper->m_signatures)<=0 ) {
+		mrmailbox_log_warning(mimeparser->m_mailbox, 0, "Message not signed.");
+		return 0;
+	}
+
+	if( expected_fingerprint == NULL ) {
+		mrmailbox_log_warning(mimeparser->m_mailbox, 0, "Fingerprint for comparison missing.");
+		return 0;
+	}
+
+	if( mrhash_find_str(mimeparser->m_e2ee_helper->m_signatures, expected_fingerprint) == NULL ) {
+		mrmailbox_log_warning(mimeparser->m_mailbox, 0, "Message does not match expected fingerprint %s.", expected_fingerprint);
+		return 0;
+	}
+
+	return 1;
+}
+
+
 static char* get_self_fingerprint(mrmailbox_t* mailbox)
 {
 	int      locked      = 0;
@@ -123,10 +194,10 @@ cleanup:
 }
 
 
-static uint32_t chat_id_2_contact_id(mrmailbox_t* mailbox, uint32_t chat_id)
+static uint32_t chat_id_2_contact_id(mrmailbox_t* mailbox, uint32_t contact_chat_id)
 {
 	uint32_t   contact_id = 0;
-	mrarray_t* contacts = mrmailbox_get_chat_contacts(mailbox, chat_id);
+	mrarray_t* contacts = mrmailbox_get_chat_contacts(mailbox, contact_chat_id);
 
 	if( mrarray_get_cnt(contacts) != 1 ) {
 		goto cleanup;
@@ -140,11 +211,11 @@ cleanup:
 }
 
 
-static int fingerprint_equals_sender(mrmailbox_t* mailbox, const char* fingerprint, uint32_t chat_id)
+static int fingerprint_equals_sender(mrmailbox_t* mailbox, const char* fingerprint, uint32_t contact_chat_id)
 {
 	int             fingerprint_equal      = 0;
 	int             locked                 = 0;
-	mrarray_t*      contacts               = mrmailbox_get_chat_contacts(mailbox, chat_id);
+	mrarray_t*      contacts               = mrmailbox_get_chat_contacts(mailbox, contact_chat_id);
 	mrcontact_t*    contact                = mrcontact_new(mailbox);
 	mrapeerstate_t* peerstate              = mrapeerstate_new(mailbox);
 	char*           fingerprint_normalized = NULL;
@@ -166,7 +237,7 @@ static int fingerprint_equals_sender(mrmailbox_t* mailbox, const char* fingerpri
 
 	fingerprint_normalized = mr_normalize_fingerprint(fingerprint);
 
-	if( strcasecmp(fingerprint_normalized, peerstate->m_fingerprint) == 0 ) {
+	if( strcasecmp(fingerprint_normalized, peerstate->m_public_key_fingerprint) == 0 ) {
 		fingerprint_equal = 1;
 	}
 
@@ -188,10 +259,16 @@ static int mark_peer_as_verified__(mrmailbox_t* mailbox, const char* fingerprint
 		goto cleanup;
 	}
 
-	if( !mrapeerstate_set_verified(peerstate, fingerprint) ) {
+	if( !mrapeerstate_set_verified(peerstate, MRA_PUBLIC_KEY, fingerprint, MRV_BIDIRECTIONAL) ) {
 		goto cleanup;
 	}
 
+	// set MUTUAL as an out-of-band-verification is a strong hint that encryption is wanted.
+	// the state may be corrected by the Autocrypt headers as usual later;
+	// maybe it is a good idea to add the prefer-encrypt-state to the QR code.
+	peerstate->m_prefer_encrypt = MRA_PE_MUTUAL;
+	peerstate->m_to_save       |= MRA_SAVE_ALL;
+
 	mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 0);
 	success = 1;
 
@@ -213,7 +290,7 @@ static const char* lookup_field(mrmimeparser_t* mimeparser, const char* key)
 }
 
 
-static void send_handshake_msg(mrmailbox_t* mailbox, uint32_t chat_id, const char* step, const char* random, const char* fingerprint)
+static void send_handshake_msg(mrmailbox_t* mailbox, uint32_t contact_chat_id, const char* step, const char* param2, const char* fingerprint, const char* grpid)
 {
 	mrmsg_t* msg = mrmsg_new();
 
@@ -223,34 +300,38 @@ static void send_handshake_msg(mrmailbox_t* mailbox, uint32_t chat_id, const cha
 	mrparam_set_int(msg->m_param, MRP_CMD,       MR_CMD_SECUREJOIN_MESSAGE);
 	mrparam_set    (msg->m_param, MRP_CMD_PARAM, step);
 
-	if( random ) {
-		mrparam_set(msg->m_param, MRP_CMD_PARAM2, random);
+	if( param2 ) {
+		mrparam_set(msg->m_param, MRP_CMD_PARAM2, param2); // depening on step, this goes either to Secure-Join-Invitenumber or Secure-Join-Auth in mrmimefactory.c
 	}
 
 	if( fingerprint ) {
 		mrparam_set(msg->m_param, MRP_CMD_PARAM3, fingerprint);
 	}
 
-	if( strcmp(step, "request") == 0 ) {
-		mrparam_set_int(msg->m_param, MRP_FORCE_UNENCRYPTED, 1); // the request message MUST NOT be encrypted - it may be that the key has changed and the message cannot be decrypted otherwise
+	if( grpid ) {
+		mrparam_set(msg->m_param, MRP_CMD_PARAM4, grpid);
+	}
+
+	if( strcmp(step, "vg-request")==0 || strcmp(step, "vc-request")==0 ) {
+		mrparam_set_int(msg->m_param, MRP_FORCE_PLAINTEXT, 1); // the request message MUST NOT be encrypted - it may be that the key has changed and the message cannot be decrypted otherwise
 	}
 	else {
 		mrparam_set_int(msg->m_param, MRP_GUARANTEE_E2EE, 1); /* all but the first message MUST be encrypted */
 	}
 
-	mrmailbox_send_msg_object(mailbox, chat_id, msg);
+	mrmailbox_send_msg_object(mailbox, contact_chat_id, msg);
 
 	mrmsg_unref(msg);
 }
 
 
-static void could_not_establish_secure_connection(mrmailbox_t* mailbox, uint32_t chat_id, const char* details)
+static void could_not_establish_secure_connection(mrmailbox_t* mailbox, uint32_t contact_chat_id, const char* details)
 {
-	uint32_t     contact_id = chat_id_2_contact_id(mailbox, chat_id);
+	uint32_t     contact_id = chat_id_2_contact_id(mailbox, contact_chat_id);
 	mrcontact_t* contact    = mrmailbox_get_contact(mailbox, contact_id);
 	char*        msg        = mr_mprintf("Could not establish secure connection to %s.", contact? contact->m_addr : "?");
 
-	mrmailbox_add_device_msg(mailbox, chat_id, msg);
+	mrmailbox_add_device_msg(mailbox, contact_chat_id, msg);
 
 	mrmailbox_log_error(mailbox, 0, "%s (%s)", msg, details); // additionaly raise an error; this typically results in a toast (inviter side) or a dialog (joiner side)
 
@@ -259,24 +340,24 @@ static void could_not_establish_secure_connection(mrmailbox_t* mailbox, uint32_t
 }
 
 
-static void secure_connection_established(mrmailbox_t* mailbox, uint32_t chat_id)
+static void secure_connection_established(mrmailbox_t* mailbox, uint32_t contact_chat_id)
 {
-	uint32_t     contact_id = chat_id_2_contact_id(mailbox, chat_id);
+	uint32_t     contact_id = chat_id_2_contact_id(mailbox, contact_chat_id);
 	mrcontact_t* contact    = mrmailbox_get_contact(mailbox, contact_id);
 	char*        msg        = mr_mprintf("Secure connection to %s established.", contact? contact->m_addr : "?");
 
-	mrmailbox_add_device_msg(mailbox, chat_id, msg);
+	mrmailbox_add_device_msg(mailbox, contact_chat_id, msg);
 
 	// in addition to MR_EVENT_MSGS_CHANGED (sent by mrmailbox_add_device_msg()), also send MR_EVENT_CHAT_MODIFIED to update all views
-	mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0);
+	mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, contact_chat_id, 0);
 
 	free(msg);
 	mrcontact_unref(contact);
 }
 
 
-#define         PLEASE_PROVIDE_RANDOM_SECRET 2
-#define         SECUREJOIN_BROADCAST         4
+#define         VC_AUTH_REQUIRED     2
+#define         VC_CONTACT_CONFIRM   6
 static int      s_bob_expects = 0;
 
 static mrlot_t* s_bobs_qr_scan = NULL; // should be surround eg. by mrsqlite3_lock/unlock
@@ -305,30 +386,37 @@ static void end_bobs_joining(mrmailbox_t* mailbox, int status)
  *
  * The scanning Delta Chat device will pass the scanned content to
  * mrmailbox_check_qr() then; if this function returns
- * MR_QR_ASK_SECUREJOIN an out-of-band-verification can be joined using
- * mrmailbox_join_securejoin()
+ * MR_QR_ASK_VERIFYCONTACT or MR_QR_ASK_VERIFYGROUP an out-of-band-verification
+ * can be joined using mrmailbox_join_securejoin()
  *
  * @memberof mrmailbox_t
  *
  * @param mailbox The mailbox object.
  *
+ * @param contact_chat_id If set to the ID of a chat, the "Joining a verified group" protocol is offered in the QR code.
+ *     If set to 0, the "Setup Verified Contact" protocol is offered in the QR code.
+ *
  * @return Text that should go to the qr code.
  */
-char* mrmailbox_get_securejoin_qr(mrmailbox_t* mailbox, uint32_t chat_id)
+char* mrmailbox_get_securejoin_qr(mrmailbox_t* mailbox, uint32_t group_chat_id)
 {
-	/* ==================================
-	   ==== Alice - the inviter side ====
-	   ================================== */
-
-	int      locked               = 0;
-	char*    qr                   = NULL;
-	char*    self_addr            = NULL;
-	char*    self_addr_urlencoded = NULL;
-	char*    self_name            = NULL;
-	char*    self_name_urlencoded = NULL;
-	char*    fingerprint          = NULL;
-	char*    random_public        = NULL;
-	char*    random_secret        = NULL;
+	/* =========================================================
+	   ====             Alice - the inviter side            ====
+	   ====   Step 1 in "Setup verified contact" protocol   ====
+	   ========================================================= */
+
+	int       locked               = 0;
+	char*     qr                   = NULL;
+	char*     self_addr            = NULL;
+	char*     self_addr_urlencoded = NULL;
+	char*     self_name            = NULL;
+	char*     self_name_urlencoded = NULL;
+	char*     fingerprint          = NULL;
+	char*     invitenumber         = NULL;
+	char*     auth                 = NULL;
+	mrchat_t* chat                 = NULL;
+	char*     group_name           = NULL;
+	char*     group_name_urlencoded= NULL;
 
 	if( mailbox == NULL || mailbox->m_magic!=MR_MAILBOX_MAGIC ) {
 		goto cleanup;
@@ -336,21 +424,22 @@ char* mrmailbox_get_securejoin_qr(mrmailbox_t* mailbox, uint32_t chat_id)
 
 	mrmailbox_ensure_secret_key_exists(mailbox);
 
-	// random_public will be used to allow starting the handshake, random_secret will be used to verify the fingerprint
-	random_public = mr_create_id();
-	random_secret = mr_create_id();
+	// invitenumber will be used to allow starting the handshake, auth will be used to verify the fingerprint
+	invitenumber  = mr_create_id();
+	auth          = mr_create_id();
 
 	mrsqlite3_lock(mailbox->m_sql);
 	locked = 1;
 
 		if( (self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", NULL)) == NULL ) {
+			mrmailbox_log_error(mailbox, 0, "Not configured.");
 			goto cleanup;
 		}
 
 		self_name = mrsqlite3_get_config__(mailbox->m_sql, "displayname", "");
 
-		store_random__(mailbox, "secureJoin.randomPublics", random_public);
-		store_random__(mailbox, "secureJoin.randomSecrets", random_secret);
+		store_tag__(mailbox, "secureJoin.invitenumbers", invitenumber);
+		store_tag__(mailbox, "secureJoin.auths", auth);
 
 	mrsqlite3_unlock(mailbox->m_sql);
 	locked = 0;
@@ -361,7 +450,24 @@ char* mrmailbox_get_securejoin_qr(mrmailbox_t* mailbox, uint32_t chat_id)
 
 	self_addr_urlencoded = mr_url_encode(self_addr);
 	self_name_urlencoded = mr_url_encode(self_name);
-	qr = mr_mprintf(OPENPGP4FPR_SCHEME "%s#v=%s&n=%s&p=%s&s=%s", fingerprint, self_addr_urlencoded, self_name_urlencoded, random_public, random_secret);
+
+	if( group_chat_id )
+	{
+		// parameters used: a=g=x=i=s=
+		chat = mrmailbox_get_chat(mailbox, group_chat_id);
+		if( chat->m_type != MR_CHAT_TYPE_VERIFIED_GROUP ) {
+			mrmailbox_log_error(mailbox, 0, "Secure join is only available for verified groups.");
+			goto cleanup;
+		}
+		group_name = mrchat_get_name(chat);
+		group_name_urlencoded = mr_url_encode(group_name);
+		qr = mr_mprintf(OPENPGP4FPR_SCHEME "%s#a=%s&g=%s&x=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, group_name_urlencoded, chat->m_grpid, invitenumber, auth);
+	}
+	else
+	{
+		// parameters used: a=n=i=s=
+		qr = mr_mprintf(OPENPGP4FPR_SCHEME "%s#a=%s&n=%s&i=%s&s=%s", fingerprint, self_addr_urlencoded, self_name_urlencoded, invitenumber, auth);
+	}
 
 cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
@@ -370,7 +476,11 @@ cleanup:
 	free(self_name);
 	free(self_name_urlencoded);
 	free(fingerprint);
-	free(random_secret);
+	free(invitenumber);
+	free(auth);
+	mrchat_unref(chat);
+	free(group_name);
+	free(group_name_urlencoded);
 	return qr? qr : safe_strdup(NULL);
 }
 
@@ -378,7 +488,7 @@ cleanup:
 /**
  * Join an out-of-band-verification initiated on another device with mrmailbox_get_securejoin_qr().
  * This function is typically called when mrmailbox_check_qr() returns
- * lot.m_state=MR_QR_ASK_SECUREJOIN
+ * lot.m_state=MR_QR_ASK_VERIFYCONTACT or lot.m_state=MR_QR_ASK_VERIFYGROUP.
  *
  * This function takes some time and sends and receives several messages.
  * You should call it in a separate thread; if you want to abort it, you should
@@ -393,18 +503,26 @@ cleanup:
  * @return 0=Out-of-band verification failed or aborted, 1=Out-of-band
  *     verification successfull, the UI may redirect to the corresponding chat
  *     where a new system message with the state was added.
+ *
+ *     TODO: check if we should say to the caller, which activity to show after
+ *     vc-request:
+ *     - for a qr-scan while group-creation, returning to the chatlist might be better
+ *     - for a qr-scan to add a contact (even without handshake), opening the created normal-chat is better
+ *     (for vg-request always the new group is shown, this is perfect)
  */
-int mrmailbox_join_securejoin(mrmailbox_t* mailbox, const char* qr)
+uint32_t mrmailbox_join_securejoin(mrmailbox_t* mailbox, const char* qr)
 {
-	/* =================================
-	   ==== Bob - the joiner's side ====
-	   ================================= */
+	/* ==========================================================
+	   ====             Bob - the joiner's side             =====
+	   ====   Step 2 in "Setup verified contact" protocol   =====
+	   ========================================================== */
 
-	int      success           = 0;
+	int      ret_chat_id       = 0;
 	int      ongoing_allocated = 0;
 	#define  CHECK_EXIT        if( mr_shall_stop_ongoing ) { goto cleanup; }
-	uint32_t chat_id           = 0;
+	uint32_t contact_chat_id   = 0;
 	mrlot_t* qr_scan           = NULL;
+	int      join_vg           = 0;
 
 	mrmailbox_log_info(mailbox, 0, "Requesting secure-join ...");
 
@@ -414,11 +532,14 @@ int mrmailbox_join_securejoin(mrmailbox_t* mailbox, const char* qr)
 		goto cleanup;
 	}
 
-	if( ((qr_scan=mrmailbox_check_qr(mailbox, qr))==NULL) || qr_scan->m_state!=MR_QR_ASK_SECUREJOIN ) {
+	if( ((qr_scan=mrmailbox_check_qr(mailbox, qr))==NULL)
+	 || (qr_scan->m_state!=MR_QR_ASK_VERIFYCONTACT && qr_scan->m_state!=MR_QR_ASK_VERIFYGROUP) ) {
+		mrmailbox_log_error(mailbox, 0, "Unknown QR code.");
 		goto cleanup;
 	}
 
-	if( (chat_id=mrmailbox_create_chat_by_contact_id(mailbox, qr_scan->m_id)) == 0 ) {
+	if( (contact_chat_id=mrmailbox_create_chat_by_contact_id(mailbox, qr_scan->m_id)) == 0 ) {
+		mrmailbox_log_error(mailbox, 0, "Unknown contact.");
 		goto cleanup;
 	}
 
@@ -431,26 +552,47 @@ int mrmailbox_join_securejoin(mrmailbox_t* mailbox, const char* qr)
 
 	CHECK_EXIT
 
-	s_bobs_status = 0;
-	s_bob_expects = PLEASE_PROVIDE_RANDOM_SECRET;
+	join_vg = (qr_scan->m_state==MR_QR_ASK_VERIFYGROUP);
 
+	s_bobs_status = 0;
 	mrsqlite3_lock(mailbox->m_sql);
 		s_bobs_qr_scan = qr_scan;
 	mrsqlite3_unlock(mailbox->m_sql);
 
-	send_handshake_msg(mailbox, chat_id, "request", qr_scan->m_random_public, NULL); // Bob -> Alice
+	if( fingerprint_equals_sender(mailbox, qr_scan->m_fingerprint, contact_chat_id) ) {
+		// the scanned fingerprint matches Alice's key, we can proceed to step 4b) directly and save two mails
+		mrmailbox_log_info(mailbox, 0, "Taking protocol shortcut.");
+		s_bob_expects = VC_CONTACT_CONFIRM;
+		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_JOINER_PROGRESS, chat_id_2_contact_id(mailbox, contact_chat_id), 4);
+		char* own_fingerprint = get_self_fingerprint(mailbox);
+		send_handshake_msg(mailbox, contact_chat_id, join_vg? "vg-request-with-auth" : "vc-request-with-auth",
+			qr_scan->m_auth, own_fingerprint, join_vg? qr_scan->m_text2 : NULL); // Bob -> Alice
+		free(own_fingerprint);
+	}
+	else {
+		s_bob_expects = VC_AUTH_REQUIRED;
+		send_handshake_msg(mailbox, contact_chat_id, join_vg? "vg-request" : "vc-request",
+			qr_scan->m_invitenumber, NULL, NULL); // Bob -> Alice
+	}
 
 	while( 1 ) {
 		CHECK_EXIT
 
-		usleep(300*1000);
+		usleep(300*1000); // 0.3 seconds
 	}
 
 cleanup:
 	s_bob_expects = 0;
 
 	if( s_bobs_status == BOB_SUCCESS ) {
-		success = 1;
+		if( join_vg ) {
+			mrsqlite3_lock(mailbox->m_sql);
+				ret_chat_id = mrmailbox_get_chat_id_by_grpid__(mailbox, qr_scan->m_text2, NULL, NULL);
+			mrsqlite3_unlock(mailbox->m_sql);
+		}
+		else {
+			ret_chat_id = contact_chat_id;
+		}
 	}
 
 	mrsqlite3_lock(mailbox->m_sql);
@@ -460,32 +602,24 @@ cleanup:
 	mrlot_unref(qr_scan);
 
 	if( ongoing_allocated ) { mrmailbox_free_ongoing(mailbox); }
-	return success;
+	return ret_chat_id;
 }
 
 
-/*
- * mrmailbox_is_securejoin_handshake__() should be called called for each
- * incoming mail. if the mail belongs to an secure-join handshake, the function
- * returns 1. The caller should unlock everything, stop normal message
- * processing and call mrmailbox_handle_securejoin_handshake() then.
- */
-int mrmailbox_is_securejoin_handshake__(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser)
+int mrmailbox_handle_securejoin_handshake(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser, uint32_t contact_id)
 {
-	if( mailbox == NULL || mimeparser == NULL || lookup_field(mimeparser, "Secure-Join") == NULL ) {
-		return 0;
-	}
-
-	return 1; /* processing is continued in mrmailbox_handle_securejoin_handshake() */
-}
-
-
-void mrmailbox_handle_securejoin_handshake(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser, uint32_t chat_id)
-{
-	int                   locked = 0;
-	const char*           step   = NULL;
-
-	if( mailbox == NULL || mimeparser == NULL || chat_id <= MR_CHAT_ID_LAST_SPECIAL ) {
+	int          locked = 0;
+	const char*  step   = NULL;
+	int          join_vg = 0;
+	char*        scanned_fingerprint_of_alice = NULL;
+	char*        auth = NULL;
+	char*        own_fingerprint = NULL;
+	uint32_t     contact_chat_id = 0;
+	int          contact_chat_id_blocked = 0;
+	char*        grpid = NULL;
+	int          ret = 0;
+
+	if( mailbox == NULL || mimeparser == NULL || contact_id <= MR_CONTACT_ID_LAST_SPECIAL ) {
 		goto cleanup;
 	}
 
@@ -494,175 +628,221 @@ void mrmailbox_handle_securejoin_handshake(mrmailbox_t* mailbox, mrmimeparser_t*
 	}
 	mrmailbox_log_info(mailbox, 0, ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message '%s' received", step);
 
-	if( strcmp(step, "request")==0 )
+	join_vg = (strncmp(step, "vg-", 3)==0);
+	LOCK
+		mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, contact_id, MR_CHAT_NOT_BLOCKED, &contact_chat_id, &contact_chat_id_blocked);
+		if( contact_chat_id_blocked ) {
+			mrmailbox_unblock_chat__(mailbox, contact_chat_id);
+		}
+	UNLOCK
+
+	ret = MR_IS_HANDSHAKE_STOP_NORMAL_PROCESSING;
+
+	if( strcmp(step, "vg-request")==0 || strcmp(step, "vc-request")==0 )
 	{
-		/* ==================================
-		   ==== Alice - the inviter side ====
-		   ================================== */
+		/* =========================================================
+		   ====             Alice - the inviter side            ====
+		   ====   Step 3 in "Setup verified contact" protocol   ====
+		   ========================================================= */
 
 		// this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet)
 
 		// it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it,
 		// send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here.
 
-		// verify that the `Secure-Join-Random-Public:`-header matches random_public written to the QR code
-		uint32_t    contact_id = chat_id_2_contact_id(mailbox, chat_id);
-		const char* random_public = NULL;
-		if( (random_public=lookup_field(mimeparser, "Secure-Join-Random-Public")) == NULL ) {
-			mrmailbox_log_warning(mailbox, 0, "Secure-join denied (random-public missing)."); // do not raise an error, this might just be spam or come from an old request
+		// verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code
+		const char* invitenumber = NULL;
+		if( (invitenumber=lookup_field(mimeparser, "Secure-Join-Invitenumber")) == NULL ) {
+			mrmailbox_log_warning(mailbox, 0, "Secure-join denied (invitenumber missing)."); // do not raise an error, this might just be spam or come from an old request
 			goto cleanup;
 		}
 
-		mrsqlite3_lock(mailbox->m_sql);
-		locked = 1;
-			if( lookup_random__(mailbox, "secureJoin.randomPublics", random_public) == 0 ) {
-				mrmailbox_log_warning(mailbox, 0, "Secure-join denied (bad random-public).");  // do not raise an error, this might just be spam or come from an old request
+		LOCK
+			if( lookup_tag__(mailbox, "secureJoin.invitenumbers", invitenumber) == 0 ) {
+				mrmailbox_log_warning(mailbox, 0, "Secure-join denied (bad invitenumber).");  // do not raise an error, this might just be spam or come from an old request
 				goto cleanup;
 			}
-		mrsqlite3_unlock(mailbox->m_sql);
-		locked = 0;
+		UNLOCK
 
 		mrmailbox_log_info(mailbox, 0, "Secure-join requested.");
 
-		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_REQUESTED, contact_id, 0);
+		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_INVITER_PROGRESS, contact_id, 3);
 
-		send_handshake_msg(mailbox, chat_id, "please-provide-random-secret", NULL, NULL); // Alice -> Bob
+		send_handshake_msg(mailbox, contact_chat_id, join_vg? "vg-auth-required" : "vc-auth-required",
+			NULL, NULL, NULL); // Alice -> Bob
 	}
-	else if( strcmp(step, "please-provide-random-secret")==0 )
+	else if( strcmp(step, "vg-auth-required")==0 || strcmp(step, "vc-auth-required")==0 )
 	{
-		/* =================================
-		   ==== Bob - the joiner's side ====
-		   ================================= */
-
-		if( !mimeparser->m_decrypted_and_validated ) {
-			could_not_establish_secure_connection(mailbox, chat_id, "Not encrypted.");
-			end_bobs_joining(mailbox, BOB_ERROR);
-			goto cleanup;
-		}
+		/* ==========================================================
+		   ====             Bob - the joiner's side             =====
+		   ====   Step 4 in "Setup verified contact" protocol   =====
+		   ========================================================== */
 
 		// verify that Alice's Autocrypt key and fingerprint matches the QR-code
-		mrsqlite3_lock(mailbox->m_sql);
-		locked = 1;
-			if( s_bobs_qr_scan == NULL || s_bob_expects != PLEASE_PROVIDE_RANDOM_SECRET ) {
+		LOCK
+			if( s_bobs_qr_scan == NULL || s_bob_expects != VC_AUTH_REQUIRED || (join_vg && s_bobs_qr_scan->m_state!=MR_QR_ASK_VERIFYGROUP) ) {
+				mrmailbox_log_warning(mailbox, 0, "auth-required message out of sync.");
 				goto cleanup; // no error, just aborted somehow or a mail from another handshake
 			}
-			char* scanned_fingerprint_of_alice = safe_strdup(s_bobs_qr_scan->m_fingerprint);
-			char* random_secret                = safe_strdup(s_bobs_qr_scan->m_random_secret);
-		mrsqlite3_unlock(mailbox->m_sql);
-		locked = 0;
+			scanned_fingerprint_of_alice = safe_strdup(s_bobs_qr_scan->m_fingerprint);
+			auth = safe_strdup(s_bobs_qr_scan->m_auth);
+			if( join_vg ) {
+				grpid = safe_strdup(s_bobs_qr_scan->m_text2);
+			}
+		UNLOCK
+
+		if( !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) ) {
+			could_not_establish_secure_connection(mailbox, contact_chat_id, mimeparser->m_e2ee_helper->m_encrypted? "No valid signature." : "Not encrypted.");
+			end_bobs_joining(mailbox, BOB_ERROR);
+			goto cleanup;
+		}
 
-		if( !fingerprint_equals_sender(mailbox, scanned_fingerprint_of_alice, chat_id) ) {
+		if( !fingerprint_equals_sender(mailbox, scanned_fingerprint_of_alice, contact_chat_id) ) {
 			// MitM?
-			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on joiner-side.");
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Fingerprint mismatch on joiner-side.");
 			end_bobs_joining(mailbox, BOB_ERROR);
 			goto cleanup;
 		}
 
 		mrmailbox_log_info(mailbox, 0, "Fingerprint verified.");
 
-		char* own_fingerprint = get_self_fingerprint(mailbox);
+		own_fingerprint = get_self_fingerprint(mailbox);
 
-		s_bob_expects = SECUREJOIN_BROADCAST;
-		send_handshake_msg(mailbox, chat_id, "random-secret", random_secret, own_fingerprint); // Bob -> Alice
+		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_JOINER_PROGRESS, contact_id, 4);
 
-		free(own_fingerprint);
-		free(scanned_fingerprint_of_alice);
-		free(random_secret);
+		s_bob_expects = VC_CONTACT_CONFIRM;
+		send_handshake_msg(mailbox, contact_chat_id, join_vg? "vg-request-with-auth" : "vc-request-with-auth",
+			auth, own_fingerprint, grpid); // Bob -> Alice
 	}
-	else if( strcmp(step, "random-secret")==0 )
+	else if( strcmp(step, "vg-request-with-auth")==0 || strcmp(step, "vc-request-with-auth")==0 )
 	{
-		/* ==================================
-		   ==== Alice - the inviter side ====
-		   ================================== */
-
-		if( !mimeparser->m_decrypted_and_validated ) {
-			could_not_establish_secure_connection(mailbox, chat_id, "Random-secret not encrypted.");
-			goto cleanup;
-		}
+		/* ============================================================
+		   ====              Alice - the inviter side              ====
+		   ====   Steps 5+6 in "Setup verified contact" protocol   ====
+		   ====  Step 6 in "Out-of-band verified groups" protocol  ====
+		   ============================================================ */
 
 		// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
 		const char* fingerprint = NULL;
 		if( (fingerprint=lookup_field(mimeparser, "Secure-Join-Fingerprint")) == NULL ) {
-			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint not provided.");
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Fingerprint not provided.");
 			goto cleanup;
 		}
 
-		if( !fingerprint_equals_sender(mailbox, fingerprint, chat_id) ) {
+		if( !encrypted_and_signed(mimeparser, fingerprint) ) {
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Auth not encrypted.");
+			goto cleanup;
+		}
+
+		if( !fingerprint_equals_sender(mailbox, fingerprint, contact_chat_id) ) {
 			// MitM?
-			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on inviter-side.");
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Fingerprint mismatch on inviter-side.");
 			goto cleanup;
 		}
 
 		mrmailbox_log_info(mailbox, 0, "Fingerprint verified.");
 
-		// verify that the `Secure-Join-Random-Secret:`-header matches the secret written to the QR code
-		const char* random_secret = NULL;
-		if( (random_secret=lookup_field(mimeparser, "Secure-Join-Random-Secret")) == NULL ) {
-			could_not_establish_secure_connection(mailbox, chat_id, "Random-secret not provided.");
+		// verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code
+		const char* auth = NULL;
+		if( (auth=lookup_field(mimeparser, "Secure-Join-Auth")) == NULL ) {
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Auth not provided.");
 			goto cleanup;
 		}
 
-		uint32_t contact_id = chat_id_2_contact_id(mailbox, chat_id);
-		mrsqlite3_lock(mailbox->m_sql);
-		locked = 1;
-			if( lookup_random__(mailbox, "secureJoin.randomSecrets", random_secret) == 0 ) {
+		LOCK
+			if( lookup_tag__(mailbox, "secureJoin.auths", auth) == 0 ) {
 				mrsqlite3_unlock(mailbox->m_sql);
 				locked = 0;
-				could_not_establish_secure_connection(mailbox, chat_id, "Random-secret invalid.");
+				could_not_establish_secure_connection(mailbox, contact_chat_id, "Auth invalid.");
 				goto cleanup;
 			}
 
 			if( !mark_peer_as_verified__(mailbox, fingerprint) ) {
 				mrsqlite3_unlock(mailbox->m_sql);
 				locked = 0;
-				could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on inviter-side."); // should not happen, we've compared the fingerprint some lines above
+				could_not_establish_secure_connection(mailbox, contact_chat_id, "Fingerprint mismatch on inviter-side."); // should not happen, we've compared the fingerprint some lines above
 				goto cleanup;
 			}
 
 			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECUREJOIN_INVITED);
-		mrsqlite3_unlock(mailbox->m_sql);
-		locked = 0;
+		UNLOCK
+
+		mrmailbox_log_info(mailbox, 0, "Auth verified.");
 
-		mrmailbox_log_info(mailbox, 0, "Random secret verified.");
+		secure_connection_established(mailbox, contact_chat_id);
 
-		secure_connection_established(mailbox, chat_id);
+		mailbox->m_cb(mailbox, MR_EVENT_CONTACTS_CHANGED, contact_id/*selected contact*/, 0);
+		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_INVITER_PROGRESS, contact_id, 6);
 
-		send_handshake_msg(mailbox, chat_id, "broadcast", NULL, NULL); // Alice -> Bob and all other group members
+		if( join_vg ) {
+			// the vg-member-added message is special: this is a normal Chat-Group-Member-Added message with an additional Secure-Join header
+			grpid = safe_strdup(lookup_field(mimeparser, "Secure-Join-Group"));
+			int is_verified = 0;
+			LOCK
+				uint32_t verified_chat_id = mrmailbox_get_chat_id_by_grpid__(mailbox, grpid, NULL, &is_verified);
+			UNLOCK
+			if( verified_chat_id == 0 || !is_verified ) {
+				mrmailbox_log_error(mailbox, 0, "Verified chat not found.");
+				goto cleanup;
+			}
+
+			mrmailbox_add_contact_to_chat4(mailbox, verified_chat_id, contact_id, 1/*from_handshake*/); // Alice -> Bob and all members
+		}
+		else {
+			send_handshake_msg(mailbox, contact_chat_id, "vc-contact-confirm",
+				NULL, NULL, NULL); // Alice -> Bob
+		}
 	}
-	else if( strcmp(step, "broadcast")==0 )
+	else if( strcmp(step, "vg-member-added")==0 || strcmp(step, "vc-contact-confirm")==0 )
 	{
-		/* =================================
-		   ==== Bob - the joiner's side ====
-		   ================================= */
+		/* ==========================================================
+		   ====             Bob - the joiner's side             =====
+		   ====   Step 7 in "Setup verified contact" protocol   =====
+		   ========================================================== */
+
+		if( join_vg ) {
+			// vg-member-added is just part of a Chat-Group-Member-Added which should be kept in any way, eg. for multi-client
+			ret = MR_IS_HANDSHAKE_CONTINUE_NORMAL_PROCESSING;
+		}
 
-		if( s_bob_expects != SECUREJOIN_BROADCAST ) {
-			mrmailbox_log_warning(mailbox, 0, "Unexpected secure-join mail order.");
-			goto cleanup; // ignore the mail without raising and error; may come from another handshake
+		if( s_bob_expects != VC_CONTACT_CONFIRM ) {
+			if( join_vg ) {
+				mrmailbox_log_info(mailbox, 0, "vg-member-added received as broadcast.");
+			}
+			else {
+				mrmailbox_log_warning(mailbox, 0, "Unexpected secure-join mail order.");
+			}
+			goto cleanup;
 		}
 
-		if( !mimeparser->m_decrypted_and_validated ) {
-			could_not_establish_secure_connection(mailbox, chat_id, "Broadcast not encrypted.");
+		LOCK
+			if( s_bobs_qr_scan == NULL || (join_vg && s_bobs_qr_scan->m_state!=MR_QR_ASK_VERIFYGROUP) ) {
+				mrmailbox_log_warning(mailbox, 0, "Message out of sync or belongs to a different handshake.");
+				goto cleanup;
+			}
+			scanned_fingerprint_of_alice = safe_strdup(s_bobs_qr_scan->m_fingerprint);
+		UNLOCK
+
+		if( !encrypted_and_signed(mimeparser, scanned_fingerprint_of_alice) ) {
+			could_not_establish_secure_connection(mailbox, contact_chat_id, "Contact confirm message not encrypted.");
 			end_bobs_joining(mailbox, BOB_ERROR);
 			goto cleanup;
 		}
 
-		uint32_t contact_id = chat_id_2_contact_id(mailbox, chat_id);
-		mrsqlite3_lock(mailbox->m_sql);
-		locked = 1;
-			if( s_bobs_qr_scan == NULL ) {
-				goto cleanup; // no error, just aborted somehow or a mail from another handshake
-			}
+		// TODO: for the broadcasted vg-member-added, make sure, the message is ours (eg. by comparing Chat-Group-Member-Added against SELF)
 
-			if( !mark_peer_as_verified__(mailbox, s_bobs_qr_scan->m_fingerprint) ) {
-				could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on joiner-side."); // MitM? - key has changed since please-provide-random-secret message
+		LOCK
+			if( !mark_peer_as_verified__(mailbox, scanned_fingerprint_of_alice) ) {
+				could_not_establish_secure_connection(mailbox, contact_chat_id, "Fingerprint mismatch on joiner-side."); // MitM? - key has changed since vc-auth-required message
 				goto cleanup;
 			}
 
 			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECUREJOIN_JOINED);
-		mrsqlite3_unlock(mailbox->m_sql);
-		locked = 0;
+		UNLOCK
 
-		secure_connection_established(mailbox, chat_id);
+		secure_connection_established(mailbox, contact_chat_id);
+
+		mailbox->m_cb(mailbox, MR_EVENT_CONTACTS_CHANGED, 0/*no select event*/, 0);
 
 		s_bob_expects = 0;
 		end_bobs_joining(mailbox, BOB_SUCCESS);
@@ -670,14 +850,23 @@ void mrmailbox_handle_securejoin_handshake(mrmailbox_t* mailbox, mrmimeparser_t*
 
 	// delete the message in 20 seconds - typical handshake last about 5 seconds, so do not disturb the connection _now_.
 	// for errors, we do not the corresoinding message at all, it may come eg. from another device or may be useful to find out what was going wrong.
-	struct mailimf_field* field;
-	if( (field=mrmimeparser_lookup_field(mimeparser, "Message-ID"))!=NULL && field->fld_type==MAILIMF_FIELD_MESSAGE_ID ) {
-		struct mailimf_message_id* fld_message_id = field->fld_data.fld_message_id;
-		if( fld_message_id && fld_message_id->mid_value ) {
-			mrjob_add__(mailbox, MRJ_DELETE_MSG_ON_IMAP, mrmailbox_rfc724_mid_exists__(mailbox, fld_message_id->mid_value, NULL, NULL), NULL, 20);
+	if( ret == MR_IS_HANDSHAKE_STOP_NORMAL_PROCESSING ) {
+		struct mailimf_field* field;
+		if( (field=mrmimeparser_lookup_field(mimeparser, "Message-ID"))!=NULL && field->fld_type==MAILIMF_FIELD_MESSAGE_ID ) {
+			struct mailimf_message_id* fld_message_id = field->fld_data.fld_message_id;
+			if( fld_message_id && fld_message_id->mid_value ) {
+				mrjob_add__(mailbox, MRJ_DELETE_MSG_ON_IMAP, mrmailbox_rfc724_mid_exists__(mailbox, fld_message_id->mid_value, NULL, NULL), NULL, 20);
+			}
 		}
 	}
 
 cleanup:
-	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
+
+	UNLOCK
+
+	free(scanned_fingerprint_of_alice);
+	free(auth);
+	free(own_fingerprint);
+	free(grpid);
+	return ret;
 }

+ 75 - 26
deltachat-ios/libraries/deltachat-core/src/mrmimefactory.c

@@ -22,6 +22,7 @@
 
 #include "mrmailbox_internal.h"
 #include "mrmimefactory.h"
+#include "mrapeerstate.h"
 
 #define LINEEND "\r\n" /* lineend used in IMF */
 
@@ -133,33 +134,49 @@ int mrmimefactory_load_msg(mrmimefactory_t* factory, uint32_t msg_id)
 			}
 			else
 			{
+				int min_verified = factory->m_chat->m_type == MR_CHAT_TYPE_VERIFIED_GROUP? MRV_BIDIRECTIONAL : MRV_NOT_VERIFIED;
+
 				sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_na_FROM_chats_contacs_JOIN_contacts_WHERE_cc,
-					"SELECT c.authname, c.addr FROM chats_contacts cc LEFT JOIN contacts c ON cc.contact_id=c.id WHERE cc.chat_id=? AND cc.contact_id>?;");
+					"SELECT c.authname, c.addr, ps.public_key_verified, ps.gossip_key_verified "
+					" FROM chats_contacts cc "
+					" LEFT JOIN contacts c ON cc.contact_id=c.id "
+					" LEFT JOIN acpeerstates ps ON c.addr=ps.addr "
+					" WHERE cc.chat_id=? AND cc.contact_id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) ";");
 				sqlite3_bind_int(stmt, 1, factory->m_msg->m_chat_id);
-				sqlite3_bind_int(stmt, 2, MR_CONTACT_ID_LAST_SPECIAL);
 				while( sqlite3_step(stmt) == SQLITE_ROW )
 				{
-					const char* authname = (const char*)sqlite3_column_text(stmt, 0);
-					const char* addr = (const char*)sqlite3_column_text(stmt, 1);
+					const char* authname            = (const char*)sqlite3_column_text(stmt, 0);
+					const char* addr                = (const char*)sqlite3_column_text(stmt, 1);
+					int         public_key_verified =              sqlite3_column_int (stmt, 2);
+					int         gossip_key_verified =              sqlite3_column_int (stmt, 3);
 					if( clist_search_string_nocase(factory->m_recipients_addr, addr)==0 )
 					{
-						clist_append(factory->m_recipients_names, (void*)((authname&&authname[0])? safe_strdup(authname) : NULL));
-						clist_append(factory->m_recipients_addr,  (void*)safe_strdup(addr));
+						if( public_key_verified >= min_verified || gossip_key_verified >= min_verified )
+						{
+							clist_append(factory->m_recipients_names, (void*)((authname&&authname[0])? safe_strdup(authname) : NULL));
+							clist_append(factory->m_recipients_addr,  (void*)safe_strdup(addr));
+						}
 					}
 				}
 
 				int command = mrparam_get_int(factory->m_msg->m_param, MRP_CMD, 0);
 				if( command==MR_CMD_MEMBER_REMOVED_FROM_GROUP /* for added members, the list is just fine */) {
-					char* email_to_remove = mrparam_get(factory->m_msg->m_param, MRP_CMD_PARAM, NULL);
-					char* self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", "");
+					char* email_to_remove     = mrparam_get(factory->m_msg->m_param, MRP_CMD_PARAM, NULL);
+					char* self_addr           = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", "");
+					mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
+					mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, email_to_remove);
 					if( email_to_remove && strcasecmp(email_to_remove, self_addr)!=0 )
 					{
 						if( clist_search_string_nocase(factory->m_recipients_addr, email_to_remove)==0 )
 						{
-							clist_append(factory->m_recipients_names, NULL);
-							clist_append(factory->m_recipients_addr,  (void*)email_to_remove);
+							if( peerstate->m_public_key_verified >= min_verified || peerstate->m_gossip_key_verified >= min_verified )
+							{
+								clist_append(factory->m_recipients_names, NULL);
+								clist_append(factory->m_recipients_addr,  (void*)email_to_remove);
+							}
 						}
 					}
+					mrapeerstate_unref(peerstate);
 					free(self_addr);
 				}
 
@@ -418,7 +435,7 @@ static char* get_subject(const mrchat_t* chat, const mrmsg_t* msg, int afwd_emai
 	{
 		ret = mrstock_str(MR_STR_AC_SETUP_MSG_SUBJECT); /* do not add the "Chat:" prefix for setup messages */
 	}
-	else if( chat->m_type==MR_CHAT_TYPE_GROUP )
+	else if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 	{
 		ret = mr_mprintf(MR_CHAT_PREFIX " %s: %s%s", chat->m_name, fwd, raw_subject);
 	}
@@ -449,7 +466,8 @@ int mrmimefactory_render(mrmimefactory_t* factory)
 	int                          parts = 0;
 	mrmailbox_e2ee_helper_t      e2ee_helper;
 	int                          e2ee_guaranteed = 0;
-	int                          force_unencrypted = 0; // 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
+	int                          min_verified = MRV_NOT_VERIFIED;
+	int                          force_plaintext = 0; // 1=add Autocrypt-header (needed eg. for handshaking), 2=no Autocrypte-header (used for MDN)
 	char*                        grpimage = NULL;
 
 	memset(&e2ee_helper, 0, sizeof(mrmailbox_e2ee_helper_t));
@@ -518,33 +536,52 @@ int mrmimefactory_render(mrmimefactory_t* factory)
 		struct mailmime* meta_part = NULL;
 		char* placeholdertext = NULL;
 
-		force_unencrypted = mrparam_get_int(factory->m_msg->m_param, MRP_FORCE_UNENCRYPTED, 0);
-		e2ee_guaranteed   = mrparam_get_int(factory->m_msg->m_param, MRP_GUARANTEE_E2EE, 0);
+		if( chat->m_type == MR_CHAT_TYPE_VERIFIED_GROUP ) {
+			mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Verified"), strdup("1")));
+			force_plaintext   = 0;
+			e2ee_guaranteed   = 1;
+			min_verified      = MRV_BIDIRECTIONAL;
+		}
+		else {
+			if( (force_plaintext = mrparam_get_int(factory->m_msg->m_param, MRP_FORCE_PLAINTEXT, 0)) == 0 ) {
+				e2ee_guaranteed = mrparam_get_int(factory->m_msg->m_param, MRP_GUARANTEE_E2EE, 0);
+			}
+		}
 
 		/* build header etc. */
 		int command = mrparam_get_int(msg->m_param, MRP_CMD, 0);
-		if( chat->m_type==MR_CHAT_TYPE_GROUP )
+		if( MR_CHAT_TYPE_IS_MULTI(chat->m_type) )
 		{
 			mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-ID"), safe_strdup(chat->m_grpid)));
 			mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-Name"), mr_encode_header_string(chat->m_name)));
 
-			if( command == MR_CMD_MEMBER_REMOVED_FROM_GROUP ) {
+
+			if( command == MR_CMD_MEMBER_REMOVED_FROM_GROUP )
+			{
 				char* email_to_remove = mrparam_get(msg->m_param, MRP_CMD_PARAM, NULL);
 				if( email_to_remove ) {
 					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-Member-Removed"), email_to_remove));
 				}
 			}
-			else if( command == MR_CMD_MEMBER_ADDED_TO_GROUP ) {
+			else if( command == MR_CMD_MEMBER_ADDED_TO_GROUP )
+			{
 				char* email_to_add = mrparam_get(msg->m_param, MRP_CMD_PARAM, NULL);
 				if( email_to_add ) {
 					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-Member-Added"), email_to_add));
 					grpimage = mrparam_get(chat->m_param, MRP_PROFILE_IMAGE, NULL);
 				}
+
+				if( mrparam_get_int(msg->m_param, MRP_CMD_PARAM2, 0) ) {
+					mrmailbox_log_info(msg->m_mailbox, 0, "sending secure-join message '%s' >>>>>>>>>>>>>>>>>>>>>>>>>", "vg-member-added");
+					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Secure-Join"), strdup("vg-member-added")));
+				}
 			}
-			else if( command == MR_CMD_GROUPNAME_CHANGED ) {
+			else if( command == MR_CMD_GROUPNAME_CHANGED )
+			{
 				mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-Name-Changed"), strdup("1")));
 			}
-			else if( command == MR_CMD_GROUPIMAGE_CHANGED ) {
+			else if( command == MR_CMD_GROUPIMAGE_CHANGED )
+			{
 				grpimage = mrparam_get(msg->m_param, MRP_CMD_PARAM, NULL);
 				if( grpimage==NULL ) {
 					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Chat-Group-Image"), safe_strdup("0")));
@@ -563,14 +600,26 @@ int mrmimefactory_render(mrmimefactory_t* factory)
 				mrmailbox_log_info(msg->m_mailbox, 0, "sending secure-join message '%s' >>>>>>>>>>>>>>>>>>>>>>>>>", step);
 				mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Secure-Join"), step/*mailimf takes ownership of string*/));
 
-				char* random_secret = mrparam_get(msg->m_param, MRP_CMD_PARAM2, NULL);
-				if( random_secret ) {
-					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strcmp(step, "request")==0? strdup("Secure-Join-Random-Public"):strdup("Secure-Join-Random-Secret"), random_secret/*mailimf takes ownership of string*/));
+				char* param2 = mrparam_get(msg->m_param, MRP_CMD_PARAM2, NULL);
+				if( param2 ) {
+					mailimf_fields_add(imf_fields, mailimf_field_new_custom(
+						(strcmp(step, "vg-request-with-auth")==0 || strcmp(step, "vc-request-with-auth")==0)?
+							strdup("Secure-Join-Auth") : strdup("Secure-Join-Invitenumber"),
+						param2/*mailimf takes ownership of string*/));
 				}
 
 				char* fingerprint = mrparam_get(msg->m_param, MRP_CMD_PARAM3, NULL);
 				if( fingerprint ) {
-					mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Secure-Join-Fingerprint"), fingerprint/*mailimf takes ownership of string*/));
+					mailimf_fields_add(imf_fields, mailimf_field_new_custom(
+						strdup("Secure-Join-Fingerprint"),
+						fingerprint/*mailimf takes ownership of string*/));
+				}
+
+				char* grpid = mrparam_get(msg->m_param, MRP_CMD_PARAM4, NULL);
+				if( grpid ) {
+					mailimf_fields_add(imf_fields, mailimf_field_new_custom(
+						strdup("Secure-Join-Group"),
+						grpid/*mailimf takes ownership of string*/));
 				}
 			}
 		}
@@ -703,7 +752,7 @@ int mrmimefactory_render(mrmimefactory_t* factory)
 		- in older versions, we did not encrypt messages to ourself when they to to SMTP - however, if these messages
 		  are forwarded for any reasons (eg. gmail always forwards to IMAP), we have no chance to decrypt them;
 		  this issue is fixed with 0.9.4 */
-		force_unencrypted = 2;
+		force_plaintext = 2;
 	}
 	else
 	{
@@ -724,8 +773,8 @@ int mrmimefactory_render(mrmimefactory_t* factory)
 	struct mailimf_subject* subject = mailimf_subject_new(mr_encode_header_string(subject_str));
 	mailimf_fields_add(imf_fields, mailimf_field_new(MAILIMF_FIELD_SUBJECT, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, subject, NULL, NULL, NULL));
 
-	if( force_unencrypted != 2 ) {
-		mrmailbox_e2ee_encrypt(factory->m_mailbox, factory->m_recipients_addr, force_unencrypted, e2ee_guaranteed, message, &e2ee_helper);
+	if( force_plaintext != 2 ) {
+		mrmailbox_e2ee_encrypt(factory->m_mailbox, factory->m_recipients_addr, force_plaintext, e2ee_guaranteed, min_verified, message, &e2ee_helper);
 	}
 
 	if( e2ee_helper.m_encryption_successfull ) {

+ 105 - 63
deltachat-ios/libraries/deltachat-core/src/mrmimeparser.c

@@ -23,6 +23,8 @@
 #include "mrmailbox_internal.h"
 #include "mrmimeparser.h"
 #include "mrmimefactory.h"
+#include "mruudecode.h"
+#include "mrpgp.h"
 #include "mrsimplify.h"
 
 
@@ -845,6 +847,7 @@ mrmimeparser_t* mrmimeparser_new(const char* blobdir, mrmailbox_t* mailbox)
 	ths->m_parts   = carray_new(16);
 	ths->m_blobdir = blobdir; /* no need to copy the string at the moment */
 	ths->m_reports = carray_new(16);
+	ths->m_e2ee_helper = calloc(1, sizeof(mrmailbox_e2ee_helper_t));
 
 	mrhash_init(&ths->m_header, MRHASH_STRING, 0/* do not copy key */);
 
@@ -872,6 +875,7 @@ void mrmimeparser_unref(mrmimeparser_t* ths)
 	mrmimeparser_empty(ths);
 	if( ths->m_parts )   { carray_free(ths->m_parts); }
 	if( ths->m_reports ) { carray_free(ths->m_reports); }
+	free(ths->m_e2ee_helper);
 	free(ths);
 }
 
@@ -932,25 +936,81 @@ void mrmimeparser_empty(mrmimeparser_t* ths)
 		carray_set_size(ths->m_reports, 0);
 	}
 
-	ths->m_decrypted_and_validated = 0;
-	ths->m_decrypted_with_validation_errors = 0;
 	ths->m_decrypting_failed = 0;
+
+	mrmailbox_e2ee_thanks(ths->m_e2ee_helper);
 }
 
 
 static void do_add_single_part(mrmimeparser_t* parser, mrmimepart_t* part)
 {
 	/* add a single part to the list of parts, the parser takes the ownership of the part, so you MUST NOT unref it after calling this function. */
-	if( parser->m_decrypted_and_validated ) {
+	if( parser->m_e2ee_helper->m_encrypted && mrhash_count(parser->m_e2ee_helper->m_signatures)>0 ) {
 		mrparam_set_int(part->m_param, MRP_GUARANTEE_E2EE, 1);
 	}
-	else if( parser->m_decrypted_with_validation_errors ) {
-		mrparam_set_int(part->m_param, MRP_ERRONEOUS_E2EE, parser->m_decrypted_with_validation_errors);
+	else if( parser->m_e2ee_helper->m_encrypted ) {
+		mrparam_set_int(part->m_param, MRP_ERRONEOUS_E2EE, MRE2EE_NO_VALID_SIGNATURE);
 	}
 	carray_add(parser->m_parts, (void*)part, NULL);
 }
 
 
+static void do_add_single_file_part(mrmimeparser_t* parser, int msg_type, int mime_type,
+                                    const char* decoded_data, size_t decoded_data_bytes,
+                                    const char* desired_filename)
+{
+	mrmimepart_t* part = NULL;
+	char*         pathNfilename = NULL;
+
+	/* create a free file name to use */
+	if( (pathNfilename=mr_get_fine_pathNfilename(parser->m_blobdir, desired_filename)) == NULL ) {
+		goto cleanup;
+	}
+
+	/* copy data to file */
+	if( mr_write_file(pathNfilename, decoded_data, decoded_data_bytes, parser->m_mailbox)==0 ) {
+		goto cleanup;
+	}
+
+	part = mrmimepart_new();
+	part->m_type  = msg_type;
+	part->m_int_mimetype = mime_type;
+	part->m_bytes = decoded_data_bytes;
+	mrparam_set(part->m_param, MRP_FILE, pathNfilename);
+	if( MR_MSG_MAKE_FILENAME_SEARCHABLE(msg_type) ) {
+		part->m_msg = mr_get_filename(pathNfilename);
+	}
+	else if( MR_MSG_MAKE_SUFFIX_SEARCHABLE(msg_type) ) {
+		part->m_msg = mr_get_filesuffix_lc(pathNfilename);
+	}
+
+	if( mime_type == MR_MIMETYPE_IMAGE ) {
+		uint32_t w = 0, h = 0;
+		if( mr_get_filemeta(decoded_data, decoded_data_bytes, &w, &h) ) {
+			mrparam_set_int(part->m_param, MRP_WIDTH, w);
+			mrparam_set_int(part->m_param, MRP_HEIGHT, h);
+		}
+	}
+
+	/* split author/title from the original filename (if we do it from the real filename, we'll also get numbers appended by mr_get_fine_pathNfilename()) */
+	if( msg_type == MR_MSG_AUDIO ) {
+		char* author = NULL, *title = NULL;
+		mrmsg_get_authorNtitle_from_filename(desired_filename, &author, &title);
+		mrparam_set(part->m_param, MRP_AUTHORNAME, author);
+		mrparam_set(part->m_param, MRP_TRACKNAME, title);
+		free(author);
+		free(title);
+	}
+
+	do_add_single_part(parser, part);
+	part = NULL;
+
+cleanup:
+	free(pathNfilename);
+	mrmimepart_unref(part);
+}
+
+
 static int mrmimeparser_add_single_part_if_known(mrmimeparser_t* ths, struct mailmime* mime)
 {
 	mrmimepart_t*                part = NULL;
@@ -958,7 +1018,6 @@ static int mrmimeparser_add_single_part_if_known(mrmimeparser_t* ths, struct mai
 
 	int                          mime_type;
 	struct mailmime_data*        mime_data;
-	char*                        pathNfilename = NULL;
 	char*                        file_suffix = NULL, *desired_filename = NULL;
 	int                          msg_type;
 
@@ -1018,7 +1077,36 @@ static int mrmimeparser_add_single_part_if_known(mrmimeparser_t* ths, struct mai
 					}
 				}
 
-				char* simplified_txt = mrsimplify_simplify(simplifier, decoded_data, decoded_data_bytes, mime_type==MR_MIMETYPE_TEXT_HTML? 1 : 0);
+				// add uuencoded stuff as MR_MSG_FILE/MR_MSG_IMAGE/etc. parts
+				char* txt = strndup(decoded_data, decoded_data_bytes);
+				{
+					char*  uu_blob = NULL, *uu_filename = NULL, *new_txt = NULL;
+					size_t uu_blob_bytes = 0;
+					int    uu_msg_type = 0, added_uu_parts = 0;
+					while( (new_txt=mruudecode_do(txt, &uu_blob, &uu_blob_bytes, &uu_filename)) != NULL )
+					{
+						mrmsg_guess_msgtype_from_suffix(uu_filename, &uu_msg_type, NULL);
+						if( uu_msg_type == 0 ) {
+							uu_msg_type = MR_MSG_FILE;
+						}
+
+						do_add_single_file_part(ths, uu_msg_type, 0, uu_blob, uu_blob_bytes, uu_filename);
+
+						free(txt);         txt = new_txt; new_txt = NULL;
+						free(uu_blob);     uu_blob = NULL; uu_blob_bytes = 0; uu_msg_type = 0;
+						free(uu_filename); uu_filename = NULL;
+
+						added_uu_parts++;
+						if( added_uu_parts > 50/*fence against endless loops*/ ) {
+							break;
+						}
+					}
+				}
+
+				// add text as MR_MSG_TEXT part
+				char* simplified_txt = mrsimplify_simplify(simplifier, txt, strlen(txt), mime_type==MR_MIMETYPE_TEXT_HTML? 1 : 0);
+				free(txt);
+				txt = NULL;
 				if( simplified_txt && simplified_txt[0] )
 				{
 					part = mrmimepart_new();
@@ -1086,48 +1174,7 @@ static int mrmimeparser_add_single_part_if_known(mrmimeparser_t* ths, struct mai
 
 				mr_replace_bad_utf8_chars(desired_filename);
 
-				/* create a free file name to use */
-				if( (pathNfilename=mr_get_fine_pathNfilename(ths->m_blobdir, desired_filename)) == NULL ) {
-					goto cleanup;
-				}
-
-				/* copy data to file */
-                if( mr_write_file(pathNfilename, decoded_data, decoded_data_bytes, ths->m_mailbox)==0 ) {
-					goto cleanup;
-                }
-
-				part = mrmimepart_new();
-				part->m_type  = msg_type;
-				part->m_int_mimetype = mime_type;
-				part->m_bytes = decoded_data_bytes;
-				mrparam_set(part->m_param, MRP_FILE, pathNfilename);
-				if( MR_MSG_MAKE_FILENAME_SEARCHABLE(msg_type) ) {
-					part->m_msg = mr_get_filename(pathNfilename);
-				}
-				else if( MR_MSG_MAKE_SUFFIX_SEARCHABLE(msg_type) ) {
-					part->m_msg = mr_get_filesuffix_lc(pathNfilename);
-				}
-
-				if( mime_type == MR_MIMETYPE_IMAGE ) {
-					uint32_t w = 0, h = 0;
-					if( mr_get_filemeta(decoded_data, decoded_data_bytes, &w, &h) ) {
-						mrparam_set_int(part->m_param, MRP_WIDTH, w);
-						mrparam_set_int(part->m_param, MRP_HEIGHT, h);
-					}
-				}
-
-				/* split author/title from the original filename (if we do it from the real filename, we'll also get numbers appended by mr_get_fine_pathNfilename()) */
-				if( msg_type == MR_MSG_AUDIO ) {
-					char* author = NULL, *title = NULL;
-					mrmsg_get_authorNtitle_from_filename(desired_filename, &author, &title);
-					mrparam_set(part->m_param, MRP_AUTHORNAME, author);
-					mrparam_set(part->m_param, MRP_TRACKNAME, title);
-					free(author);
-					free(title);
-				}
-
-				do_add_single_part(ths, part);
-				part = NULL;
+				do_add_single_file_part(ths, msg_type, mime_type, decoded_data, decoded_data_bytes, desired_filename);
 			}
 			break;
 
@@ -1140,7 +1187,6 @@ cleanup:
 	mrsimplify_unref(simplifier);
 	if( charset_buffer ) { charconv_buffer_free(charset_buffer); }
 	if( transfer_decoding_buffer ) { mmap_string_unref(transfer_decoding_buffer); }
-	free(pathNfilename);
 	free(file_suffix);
 	free(desired_filename);
 	mrmimepart_unref(part);
@@ -1235,7 +1281,11 @@ static int mrmimeparser_parse_mime_recursive(mrmimeparser_t* ths, struct mailmim
 					{
 						mrmimepart_t* part = mrmimepart_new();
 						part->m_type = MR_MSG_TEXT;
-						part->m_msg = mrstock_str(MR_STR_ENCRYPTEDMSG); /* not sure if the text "Encrypted message" is 100% sufficient here (bp) */
+
+						char* msg_body = mrstock_str(MR_STR_CANTDECRYPT_MSG_BODY);
+						part->m_msg = mr_mprintf(MR_EDITORIAL_OPEN "%s" MR_EDITORIAL_CLOSE, msg_body);
+						free(msg_body);
+
 						carray_add(ths->m_parts, (void*)part, NULL);
 						any_part_added = 1;
 						ths->m_decrypting_failed = 1;
@@ -1421,15 +1471,7 @@ void mrmimeparser_parse(mrmimeparser_t* ths, const char* body_not_terminated, si
 
 	/* decrypt, if possible; handle Autocrypt:-header
 	(decryption may modifiy the given object) */
-	int validation_errors = 0;
-	if( mrmailbox_e2ee_decrypt(ths->m_mailbox, ths->m_mimeroot, &validation_errors, &ths->m_degrade_event) ) {
-		if( validation_errors == 0 ) {
-			ths->m_decrypted_and_validated = 1;
-		}
-		else {
-			ths->m_decrypted_with_validation_errors = validation_errors;
-		}
-	}
+	mrmailbox_e2ee_decrypt(ths->m_mailbox, ths->m_mimeroot, ths->m_e2ee_helper);
 
 	//printf("after decryption:\n"); mailmime_print(ths->m_mimeroot);
 
@@ -1636,7 +1678,7 @@ cleanup:
  */
 struct mailimf_field* mrmimeparser_lookup_field(mrmimeparser_t* mimeparser, const char* field_name)
 {
-	return (struct mailimf_field*)mrhash_find(&mimeparser->m_header, field_name, strlen(field_name));
+	return (struct mailimf_field*)mrhash_find_str(&mimeparser->m_header, field_name);
 }
 
 
@@ -1656,7 +1698,7 @@ struct mailimf_field* mrmimeparser_lookup_field(mrmimeparser_t* mimeparser, cons
  */
 struct mailimf_optional_field* mrmimeparser_lookup_optional_field(mrmimeparser_t* mimeparser, const char* field_name)
 {
-	struct mailimf_field* field = mrhash_find(&mimeparser->m_header, field_name, strlen(field_name));
+	struct mailimf_field* field = mrhash_find_str(&mimeparser->m_header, field_name);
 	if( field && field->fld_type == MAILIMF_FIELD_OPTIONAL_FIELD ) {
 		return field->fld_data.fld_optional_field;
 	}
@@ -1826,7 +1868,7 @@ int mrmimeparser_sender_equals_recipient(mrmimeparser_t* mimeparser)
 	}
 
 	/* check if From: == To:/Cc: */
-	if( mrhash_find(recipients, from_addr_norm, strlen(from_addr_norm)) ) {
+	if( mrhash_find_str(recipients, from_addr_norm) ) {
 		sender_equals_recipient = 1;
 	}
 

+ 7 - 4
deltachat-ios/libraries/deltachat-core/src/mrmimeparser.h

@@ -36,6 +36,9 @@ extern "C" {
 #include "mrparam.h"
 
 
+typedef struct mrmailbox_e2ee_helper_t mrmailbox_e2ee_helper_t;
+
+
 typedef struct mrmimepart_t
 {
 	/** @privatesection */
@@ -64,9 +67,11 @@ typedef struct mrmimeparser_t
 
 	char*                  m_subject;
 	int                    m_is_send_by_messenger;
-	int                    m_decrypted_and_validated;
-	int                    m_decrypted_with_validation_errors;
+
 	int                    m_decrypting_failed; /* set, if there are multipart/encrypted parts left after decryption */
+
+	mrmailbox_e2ee_helper_t* m_e2ee_helper;
+
 	const char*            m_blobdir;
 
 	int                    m_is_forwarded;
@@ -77,8 +82,6 @@ typedef struct mrmimeparser_t
 
 	int                    m_is_system_message;
 
-	int                    m_degrade_event;
-
 } mrmimeparser_t;
 
 

+ 35 - 16
deltachat-ios/libraries/deltachat-core/src/mrmsg.c

@@ -380,12 +380,11 @@ char* mrmsg_get_filemime(const mrmsg_t* msg)
 
 	ret = mrparam_get(msg->m_param, MRP_MIMETYPE, NULL);
 	if( ret == NULL ) {
-		int dummy_msgtype = 0;
 		file = mrparam_get(msg->m_param, MRP_FILE, NULL);
 		if( file == NULL ) {
 			goto cleanup;
 		}
-		mrmsg_guess_msgtype_from_suffix(file, &dummy_msgtype, &ret);
+		mrmsg_guess_msgtype_from_suffix(file, NULL, &ret);
 
 		if( ret == NULL ) {
 			ret = safe_strdup("application/octet-stream");
@@ -581,11 +580,22 @@ int mrmsg_get_duration(const mrmsg_t* msg)
 int mrmsg_get_showpadlock(const mrmsg_t* msg)
 {
 	/* a padlock guarantees that the message is e2ee _and_ answers will be as well */
-	if( msg == NULL || msg->m_magic != MR_MSG_MAGIC ) {
+	int show_encryption_state = 0;
+
+	if( msg == NULL || msg->m_magic != MR_MSG_MAGIC || msg->m_mailbox == NULL ) {
 		return 0;
 	}
 
-	if( msg->m_mailbox && msg->m_mailbox->m_e2ee_enabled ) {
+	if( msg->m_mailbox->m_e2ee_enabled ) {
+		show_encryption_state = 1;
+	}
+	else {
+		mrchat_t* chat = mrmailbox_get_chat(msg->m_mailbox, msg->m_chat_id);
+		show_encryption_state = mrchat_is_verified(chat);
+		mrchat_unref(chat);
+	}
+
+	if( show_encryption_state ) {
 		if( mrparam_get_int(msg->m_param, MRP_GUARANTEE_E2EE, 0) != 0 ) {
 			return 1;
 		}
@@ -641,7 +651,7 @@ mrlot_t* mrmsg_get_summary(const mrmsg_t* msg, const mrchat_t* chat)
 		chat = chat_to_delete;
 	}
 
-	if( msg->m_from_id != MR_CONTACT_ID_SELF  &&  chat->m_type == MR_CHAT_TYPE_GROUP ) {
+	if( msg->m_from_id != MR_CONTACT_ID_SELF && MR_CHAT_TYPE_IS_MULTI(chat->m_type) ) {
 		contact = mrmailbox_get_contact(chat->m_mailbox, msg->m_from_id);
 	}
 
@@ -755,7 +765,7 @@ int mrmsg_is_forwarded(const mrmsg_t* msg)
  * created due to other actions, eg. mrmailbox_set_chat_name(), mrmailbox_set_chat_profile_image()
  * or mrmailbox_add_contact_to_chat().
  *
- * These messages are typically shown i n the center of the chat view,
+ * These messages are typically shown in the center of the chat view,
  * mrmsg_get_text() returns a descriptive text about what is going on.
  *
  * There is no need to perfrom any action when seeing such a message - this is already done by the core.
@@ -950,6 +960,7 @@ int mrmsg_load_from_db__(mrmsg_t* ths, mrmailbox_t* mailbox, uint32_t id)
  * @param pathNfilename Path and filename of the file to guess the type for.
  *
  * @param[out] ret_msgtype Guessed message type is copied here as one of the MR_MSG_* constants.
+ *     May be NULL if you're not interested in this value.
  *
  * @param[out] ret_mime The pointer to a string buffer is set to the guessed MIME-type. May be NULL. Must be free()'d by the caller.
  *
@@ -957,41 +968,49 @@ int mrmsg_load_from_db__(mrmsg_t* ths, mrmailbox_t* mailbox, uint32_t id)
  */
 void mrmsg_guess_msgtype_from_suffix(const char* pathNfilename, int* ret_msgtype, char** ret_mime)
 {
-	if( pathNfilename == NULL || ret_msgtype == NULL || ret_mime == NULL) {
-		return;
+	char* suffix = NULL;
+	int   dummy_msgtype = 0;
+	char* dummy_buf = NULL;
+
+	if( pathNfilename == NULL ) {
+		goto cleanup;
 	}
 
+	if( ret_msgtype == NULL ) { ret_msgtype = &dummy_msgtype; }
+	if( ret_mime == NULL )    { ret_mime = &dummy_buf; }
+
 	*ret_msgtype = MR_MSG_UNDEFINED;
 	*ret_mime = NULL;
 
-	char* s = mr_get_filesuffix_lc(pathNfilename);
-	if( s == NULL ) {
+	suffix = mr_get_filesuffix_lc(pathNfilename);
+	if( suffix == NULL ) {
 		goto cleanup;
 	}
 
-	if( strcmp(s, "mp3")==0 ) {
+	if( strcmp(suffix, "mp3")==0 ) {
 		*ret_msgtype = MR_MSG_AUDIO;
 		*ret_mime = safe_strdup("audio/mpeg");
 	}
-	else if( strcmp(s, "mp4")==0 ) {
+	else if( strcmp(suffix, "mp4")==0 ) {
 		*ret_msgtype = MR_MSG_VIDEO;
 		*ret_mime = safe_strdup("video/mp4");
 	}
-	else if( strcmp(s, "jpg")==0 || strcmp(s, "jpeg")==0 ) {
+	else if( strcmp(suffix, "jpg")==0 || strcmp(suffix, "jpeg")==0 ) {
 		*ret_msgtype = MR_MSG_IMAGE;
 		*ret_mime = safe_strdup("image/jpeg");
 	}
-	else if( strcmp(s, "png")==0 ) {
+	else if( strcmp(suffix, "png")==0 ) {
 		*ret_msgtype = MR_MSG_IMAGE;
 		*ret_mime = safe_strdup("image/png");
 	}
-	else if( strcmp(s, "gif")==0 ) {
+	else if( strcmp(suffix, "gif")==0 ) {
 		*ret_msgtype = MR_MSG_GIF;
 		*ret_mime = safe_strdup("image/gif");
 	}
 
 cleanup:
-	free(s);
+	free(suffix);
+	free(dummy_buf);
 }
 
 

+ 2 - 1
deltachat-ios/libraries/deltachat-core/src/mrparam.h

@@ -52,13 +52,14 @@ typedef struct mrparam_t
 #define MRP_TRACKNAME         'n'  /* for msgs: name of author or artist */
 #define MRP_GUARANTEE_E2EE    'c'  /* for msgs: incoming: message is encryoted, outgoing: guarantee E2EE or the message is not send */
 #define MRP_ERRONEOUS_E2EE    'e'  /* for msgs: decrypted with validation errors or without mutual set, if neither 'c' nor 'e' are preset, the messages is only transport encrypted */
-#define MRP_FORCE_UNENCRYPTED 'u'  /* for msgs: force unencrypted message, 1=add Autocrypt header, 2=no Autocrypt header */
+#define MRP_FORCE_PLAINTEXT   'u'  /* for msgs: force unencrypted message, 1=add Autocrypt header, 2=no Autocrypt header */
 #define MRP_WANTS_MDN         'r'  /* for msgs: an incoming message which requestes a MDN (aka read receipt) */
 #define MRP_FORWARDED         'a'  /* for msgs */
 #define MRP_CMD               'S'  /* for msgs */
 #define MRP_CMD_PARAM         'E'  /* for msgs */
 #define MRP_CMD_PARAM2        'F'  /* for msgs */
 #define MRP_CMD_PARAM3        'G'  /* for msgs */
+#define MRP_CMD_PARAM4        'H'  /* for msgs */
 
 #define MRP_SERVER_FOLDER     'Z'  /* for jobs */
 #define MRP_SERVER_UID        'z'  /* for jobs */

+ 30 - 28
deltachat-ios/libraries/deltachat-core/src/mrpgp.c

@@ -45,6 +45,7 @@ one :-) */
 #include "mrkey.h"
 #include "mrkeyring.h"
 #include "mrpgp.h"
+#include "mrhash.h"
 
 
 static pgp_io_t s_io;
@@ -314,7 +315,7 @@ int mrpgp_create_keypair(mrmailbox_t* mailbox, const char* addr, mrkey_t* ret_pu
 	}
 
 	/* Generate User ID.  For convention, use the same address as given in `Autocrypt: to=...` in angle brackets
-	(RFC 2822 grammar angle-addr, see also https://autocrypt.org/en/latest/level0.html#type-p-openpgp-based-key-data )
+	(RFC 2822 grammar angle-addr, see also https://autocrypt.org/level1.html#openpgp-based-key-data )
 	We do not add the name to the ID for the following reasons:
 	- privacy
 	- the name may be changed
@@ -622,11 +623,11 @@ int mrpgp_pk_decrypt(  mrmailbox_t*       mailbox,
                        const void*        ctext,
                        size_t             ctext_bytes,
                        const mrkeyring_t* raw_private_keys_for_decryption,
-                       const mrkey_t*     raw_public_key_for_validation,
+                       const mrkeyring_t* raw_public_keys_for_validation,
                        int                use_armor,
                        void**             ret_plain,
                        size_t*            ret_plain_bytes,
-                       int*               ret_validation_errors)
+                       mrhash_t*          ret_signature_fingerprints)
 {
 	pgp_keyring_t*    public_keys = calloc(1, sizeof(pgp_keyring_t)); /*should be 0 after parsing*/
 	pgp_keyring_t*    private_keys = calloc(1, sizeof(pgp_keyring_t));
@@ -637,7 +638,7 @@ int mrpgp_pk_decrypt(  mrmailbox_t*       mailbox,
 	pgp_memory_t*     keysmem = pgp_memory_new();
 	int               i, success = 0;
 
-	if( mailbox==NULL || ctext==NULL || ctext_bytes==0 || ret_plain==NULL || ret_plain_bytes==NULL || ret_validation_errors==NULL
+	if( mailbox==NULL || ctext==NULL || ctext_bytes==0 || ret_plain==NULL || ret_plain_bytes==NULL
 	 || raw_private_keys_for_decryption==NULL || raw_private_keys_for_decryption->m_count<=0
 	 || vresult==NULL || keysmem==NULL || public_keys==NULL || private_keys==NULL ) {
 		goto cleanup;
@@ -658,10 +659,12 @@ int mrpgp_pk_decrypt(  mrmailbox_t*       mailbox,
 		goto cleanup;
 	}
 
-	if( raw_public_key_for_validation ) {
-		pgp_memory_clear(keysmem);
-		pgp_memory_add(keysmem, raw_public_key_for_validation->m_binary, raw_public_key_for_validation->m_bytes);
-		pgp_filter_keys_from_mem(&s_io, public_keys, dummy_keys/*should stay empty*/, NULL, 0, keysmem);
+	if( raw_public_keys_for_validation ) {
+		for( i = 0; i < raw_public_keys_for_validation->m_count; i++ ) {
+			pgp_memory_clear(keysmem);
+			pgp_memory_add(keysmem, raw_public_keys_for_validation->m_keys[i]->m_binary, raw_public_keys_for_validation->m_keys[i]->m_bytes);
+			pgp_filter_keys_from_mem(&s_io, public_keys, dummy_keys/*should stay empty*/, NULL, 0, keysmem);
+		}
 	}
 
 	/* decrypt */
@@ -676,27 +679,26 @@ int mrpgp_pk_decrypt(  mrmailbox_t*       mailbox,
 		*ret_plain_bytes = outmem->length;
 		free(outmem); /* do not use pgp_memory_free() as we took ownership of the buffer */
 
-		/* validate */
-		*ret_validation_errors = 0;
-		if( vresult->validc <= 0 && vresult->invalidc <= 0 && vresult->unknownc <= 0 )
-		{
-			/* no valid nor invalid signatures found */
-			*ret_validation_errors = MR_VALIDATE_NO_SIGNATURE;
-		}
-		else if( raw_public_key_for_validation==NULL || vresult->unknownc > 0 )
-		{
-			/* at least one valid or invalid signature found, but no key for verification */
-			*ret_validation_errors = MR_VALIDATE_UNKNOWN_SIGNATURE;
-		}
-		else if( vresult->invalidc > 0 )
+		// collect the keys of the valid signatures
+		if( ret_signature_fingerprints )
 		{
-			/* at least one invalid signature found */
-			*ret_validation_errors = MR_VALIDATE_BAD_SIGNATURE;
-		}
-		else
-		{
-			/* only valid signatures found */
-			;
+			for( i = 0; i < vresult->validc; i++ )
+			{
+				unsigned from = 0;
+				pgp_key_t* key0 = pgp_getkeybyid(&s_io, public_keys, vresult->valid_sigs[i].signer_id, &from, NULL, NULL, 0, 0);
+				if( key0 ) {
+					pgp_pubkey_t* pubkey0 = &key0->key.pubkey;
+					if( !pgp_fingerprint(&key0->pubkeyfpr, pubkey0, 0) ) {
+						goto cleanup;
+					}
+
+					char* fingerprint_hex = mr_binary_fingerprint_to_uc_hex(key0->pubkeyfpr.fingerprint, key0->pubkeyfpr.length);
+					if( fingerprint_hex ) {
+						mrhash_insert(ret_signature_fingerprints, fingerprint_hex, strlen(fingerprint_hex), (void*)1);
+					}
+					free(fingerprint_hex);
+				}
+			}
 		}
 	}
 

+ 2 - 4
deltachat-ios/libraries/deltachat-core/src/mrpgp.h

@@ -34,9 +34,7 @@ typedef struct mrkeyring_t mrkeyring_t;
 
 
 /* validation errors */
-#define MR_VALIDATE_NO_SIGNATURE      0x01
-#define MR_VALIDATE_UNKNOWN_SIGNATURE 0x02
-#define MR_VALIDATE_BAD_SIGNATURE     0x04
+#define MRE2EE_NO_VALID_SIGNATURE 0x02
 
 /* misc. */
 void mrpgp_init             (mrmailbox_t*);
@@ -51,7 +49,7 @@ int  mrpgp_calc_fingerprint (const mrkey_t*, uint8_t** fingerprint, size_t* fing
 int  mrpgp_split_key        (mrmailbox_t*, const mrkey_t* private_in, mrkey_t* public_out);
 
 int  mrpgp_pk_encrypt       (mrmailbox_t*, const void* plain, size_t plain_bytes, const mrkeyring_t*, const mrkey_t* sign_key, int use_armor, void** ret_ctext, size_t* ret_ctext_bytes);
-int  mrpgp_pk_decrypt       (mrmailbox_t*, const void* ctext, size_t ctext_bytes, const mrkeyring_t*, const mrkey_t* validate_key, int use_armor, void** plain, size_t* plain_bytes, int* ret_validation_errors);
+int  mrpgp_pk_decrypt       (mrmailbox_t*, const void* ctext, size_t ctext_bytes, const mrkeyring_t*, const mrkeyring_t* validate_keys, int use_armor, void** plain, size_t* plain_bytes, mrhash_t* ret_signature_fingerprints);
 
 
 #ifdef __cplusplus

+ 2 - 2
deltachat-ios/libraries/deltachat-core/src/mrsimplify.c

@@ -252,7 +252,7 @@ static char* mrsimplify_simplify_plain_text(mrsimplify_t* ths, const char* buf_t
 	mrstrbuilder_init(&ret, strlen(buf_terminated));
 
 	if( ths->m_is_cut_at_begin ) {
-		mrstrbuilder_cat(&ret, MR_ELLIPSE_STR " ");
+		mrstrbuilder_cat(&ret, MR_EDITORIAL_ELLIPSE " ");
 	}
 
 	int pending_linebreaks = 0; /* we write empty lines only in case and non-empty line follows */
@@ -285,7 +285,7 @@ static char* mrsimplify_simplify_plain_text(mrsimplify_t* ths, const char* buf_t
 
 	if( ths->m_is_cut_at_end
 	 && (!ths->m_is_cut_at_begin || content_lines_added) /* avoid two `[...]` without content */ ) {
-		mrstrbuilder_cat(&ret, " " MR_ELLIPSE_STR);
+		mrstrbuilder_cat(&ret, " " MR_EDITORIAL_ELLIPSE);
 	}
 
 	mr_free_splitted_lines(lines);

+ 34 - 9
deltachat-ios/libraries/deltachat-core/src/mrsqlite3.c

@@ -70,13 +70,13 @@ sqlite3_stmt* mrsqlite3_prepare_v2_(mrsqlite3_t* ths, const char* querystr)
 	if( sqlite3_prepare_v2(ths->m_cobj,
 	         querystr, -1 /*read `sql` up to the first null-byte*/,
 	         &retStmt,
-	         NULL /*tail not interesing, we use only single statements*/) != SQLITE_OK )
+	         NULL /*tail not interesting, we use only single statements*/) != SQLITE_OK )
 	{
 		mrsqlite3_log_error(ths, "Query failed: %s", querystr);
 		return NULL;
 	}
 
-	/* success - the result mus be freed using sqlite3_finalize() */
+	/* success - the result must be freed using sqlite3_finalize() */
 	return retStmt;
 }
 
@@ -158,7 +158,7 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 	}
 
 	if( ths->m_cobj ) {
-		mrmailbox_log_error(ths->m_mailbox, 0, "Cannot open, database \"%s\" already opend.", dbfile);
+		mrmailbox_log_error(ths->m_mailbox, 0, "Cannot open, database \"%s\" already opened.", dbfile);
 		goto cleanup;
 	}
 
@@ -203,7 +203,7 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 			mrsqlite3_execute__(ths, "CREATE TABLE chats_contacts (chat_id INTEGER, contact_id INTEGER);");
 			mrsqlite3_execute__(ths, "CREATE INDEX chats_contacts_index1 ON chats_contacts (chat_id);");
 			mrsqlite3_execute__(ths, "INSERT INTO chats (id,type,name) VALUES (1,120,'deaddrop'), (2,120,'rsvd'), (3,120,'trash'), (4,120,'msgs_in_creation'), (5,120,'starred'), (6,120,'archivedlink'), (7,100,'rsvd'), (8,100,'rsvd'), (9,100,'rsvd');");
-			#if !defined(MR_CHAT_TYPE_NORMAL) || MR_CHAT_TYPE_NORMAL!=100 || MR_CHAT_TYPE_GROUP!=120 || \
+			#if !defined(MR_CHAT_TYPE_SINGLE) || MR_CHAT_TYPE_SINGLE!=100 || MR_CHAT_TYPE_GROUP!=120 || \
 			 MR_CHAT_ID_DEADDROP!=1 || MR_CHAT_ID_TRASH!=3 || \
 			 MR_CHAT_ID_MSGS_IN_CREATION!=4 || MR_CHAT_ID_STARRED!=5 || MR_CHAT_ID_ARCHIVED_LINK!=6 || \
 			 MR_CHAT_NOT_BLOCKED!=0  || MR_CHAT_MANUALLY_BLOCKED!=1 || MR_CHAT_DEADDROP_BLOCKED!=2
@@ -355,19 +355,23 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 				mrsqlite3_execute__(ths, "CREATE INDEX chats_contacts_index2 ON chats_contacts (contact_id);"); /* needed to find chat by contact list */
 				mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
 				mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN timestamp_rcvd INTEGER DEFAULT 0;");
-				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
-				mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index2 ON acpeerstates (fingerprint);");
 
 				dbversion = NEW_DB_VERSION;
 				mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
 			}
 		#undef NEW_DB_VERSION
 
-		#define NEW_DB_VERSION 31
+		#define NEW_DB_VERSION 34
 			if( dbversion < NEW_DB_VERSION )
 			{
 				mrsqlite3_execute__(ths, "ALTER TABLE msgs ADD COLUMN hidden INTEGER DEFAULT 0;");
-				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN verified INTEGER DEFAULT 0;");
+				mrsqlite3_execute__(ths, "ALTER TABLE msgs_mdns ADD COLUMN timestamp_sent INTEGER DEFAULT 0;");
+				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN public_key_fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
+				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN public_key_verified INTEGER DEFAULT 0;");
+				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN gossip_key_fingerprint TEXT DEFAULT '';"); /* do not add `COLLATE NOCASE` case-insensivity is not needed as we force uppercase on store - otoh case-sensivity may be neeed for other/upcoming fingerprint formats */
+				mrsqlite3_execute__(ths, "ALTER TABLE acpeerstates ADD COLUMN gossip_key_verified INTEGER DEFAULT 0;");
+				mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index3 ON acpeerstates (public_key_fingerprint);");
+				mrsqlite3_execute__(ths, "CREATE INDEX acpeerstates_index4 ON acpeerstates (gossip_key_fingerprint);");
 				recalc_fingerprints = 1;
 
 				dbversion = NEW_DB_VERSION;
@@ -436,7 +440,7 @@ int mrsqlite3_is_open(const mrsqlite3_t* ths)
 
 sqlite3_stmt* mrsqlite3_predefine__(mrsqlite3_t* ths, size_t idx, const char* querystr)
 {
-	/* predefines a statement or resets and reuses a statment.
+	/* predefines a statement or resets and reuses a statement.
 	Subsequent call may ommit the querystring.
 	CAVE: you must not call this function with different strings for the same index! */
 
@@ -632,18 +636,39 @@ int mrsqlite3_set_config_int__(mrsqlite3_t* ths, const char* key, int32_t value)
  ******************************************************************************/
 
 
+#ifdef MR_USE_LOCK_DEBUG
+void mrsqlite3_lockNdebug(mrsqlite3_t* ths, const char* filename, int linenum) /* wait and lock */
+#else
 void mrsqlite3_lock(mrsqlite3_t* ths) /* wait and lock */
+#endif
 {
+	#ifdef MR_USE_LOCK_DEBUG
+		clock_t start = clock();
+		mrmailbox_log_info(ths->m_mailbox, 0, "    waiting for lock at %s#L%i", filename, linenum);
+	#endif
+
 	pthread_mutex_lock(&ths->m_critical_);
 
+	#ifdef MR_USE_LOCK_DEBUG
+		mrmailbox_log_info(ths->m_mailbox, 0, "{{{ LOCK AT %s#L%i after %.3f ms", filename, linenum, (double)(clock()-start)*1000.0/CLOCKS_PER_SEC);
+	#endif
+
 	//mrmailbox_wake_lock(ths->m_mailbox);
 }
 
 
+#ifdef MR_USE_LOCK_DEBUG
+void mrsqlite3_unlockNdebug(mrsqlite3_t* ths, const char* filename, int linenum)
+#else
 void mrsqlite3_unlock(mrsqlite3_t* ths)
+#endif
 {
 	//mrmailbox_wake_unlock(ths->m_mailbox);
 
+	#ifdef MR_USE_LOCK_DEBUG
+		mrmailbox_log_info(ths->m_mailbox, 0, "    UNLOCK AT %s#L%i }}}", filename, linenum);
+	#endif
+
 	pthread_mutex_unlock(&ths->m_critical_);
 }
 

+ 9 - 1
deltachat-ios/libraries/deltachat-core/src/mrsqlite3.h

@@ -68,6 +68,7 @@ enum
 	,SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_archived
 	,SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_unarchived
 	,SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_query
+	,SELECT_ii_FROM_chats_LEFT_JOIN_msgs_WHERE_contact_id
 	,SELECT_itndd_FROM_chats_WHERE_i
 	,SELECT_id_FROM_chats_WHERE_id
 	,SELECT_id_FROM_chats_WHERE_contact_id
@@ -88,7 +89,7 @@ enum
 	,SELECT_COUNT_FROM_chats_contacts_WHERE_contact_id
 	,SELECT_verified_FROM_chats_contacts_WHERE_chat_id
 	,SELECT_c_FROM_chats_contacts_WHERE_c
-	,SELECT_c_FROM_chats_contacts_WHERE_c_ORDER_BY
+	,SELECT_contact_id_FROM_chats_contacts_WHERE_chat_id_ORDER_BY
 	,SELECT_void_FROM_chats_contacts_WHERE_chat_id_AND_contact_id
 	,INSERT_INTO_chats_contacts
 
@@ -203,8 +204,15 @@ void          mrsqlite3_reset_all_predefinitions(mrsqlite3_t*);
 the user of MrSqlite3 must make sure that the MrSqlite3-object is only used by one thread at the same time.
 In general, we will lock the hightest level as possible - this avoids deadlocks and massive on/off lockings.
 Low-level-functions, eg. the MrSqlite3-methods, do not lock. */
+#ifdef MR_USE_LOCK_DEBUG
+#define       mrsqlite3_lock(a)          mrsqlite3_lockNdebug((a), __FILE__, __LINE__)
+#define       mrsqlite3_unlock(a)        mrsqlite3_unlockNdebug((a), __FILE__, __LINE__)
+void          mrsqlite3_lockNdebug       (mrsqlite3_t*, const char* filename, int line);
+void          mrsqlite3_unlockNdebug     (mrsqlite3_t*, const char* filename, int line);
+#else
 void          mrsqlite3_lock             (mrsqlite3_t*); /* lock or wait; these calls must not be nested in a single thread */
 void          mrsqlite3_unlock           (mrsqlite3_t*);
+#endif
 
 /* nestable transactions, only the outest is really used */
 void          mrsqlite3_begin_transaction__(mrsqlite3_t*);

+ 3 - 5
deltachat-ios/libraries/deltachat-core/src/mrstock.c

@@ -61,22 +61,20 @@ static char* default_string(int id, int qty)
 		case MR_STR_MSGGROUPLEFT:          return safe_strdup("Group left.");
 		case MR_STR_SELFNOTINGRP:          return safe_strdup("You must be a member of the group to perform this action.");
 		case MR_STR_NONETWORK:             return safe_strdup("No network available.");
-		case MR_STR_ENCR_E2E:              return safe_strdup("End-to-end encryption enabled.");
+		case MR_STR_E2E_AVAILABLE:         return safe_strdup("End-to-end encryption available.");
 		case MR_STR_ENCR_TRANSP:           return safe_strdup("Transport-encryption.");
 		case MR_STR_ENCR_NONE:             return safe_strdup("No encryption.");
 		case MR_STR_FINGERPRINTS:          return safe_strdup("Fingerprints");
 		case MR_STR_READRCPT:              return safe_strdup("Return receipt");
 		case MR_STR_READRCPT_MAILBODY:     return safe_strdup("This is a return receipt for the message \"%1$s\".");
 		case MR_STR_MSGGRPIMGDELETED:      return safe_strdup("Group image deleted.");
-		case MR_STR_E2E_FINE:              return safe_strdup("Please check, if all fingerprints match.");
-		case MR_STR_E2E_NO_AUTOCRYPT:      return safe_strdup("E2EE will be enabled automatically.");
-		case MR_STR_E2E_DIS_BY_YOU:        return safe_strdup("E2EE will be endable if you enable the corresponding option.");
-		case MR_STR_E2E_DIS_BY_RCPT:       return safe_strdup("E2EE will be enabled if the recipients enables the corresponding option.");/* do not say, the recipient has _disabled_ the option, this may not be true! */
+		case MR_STR_E2E_PREFERRED:         return safe_strdup("End-to-end encryption preferred.");
 		case MR_STR_ARCHIVEDCHATS:         return safe_strdup("Archived chats");
 		case MR_STR_STARREDMSGS:           return safe_strdup("Starred messages");
 		case MR_STR_AC_SETUP_MSG_SUBJECT:  return safe_strdup("Autocrypt Setup Message");
 		case MR_STR_AC_SETUP_MSG_BODY:     return safe_strdup("This is the Autocrypt Setup Message used to transfer your key between clients.\n\nTo decrypt and use your key, open the message in an Autocrypt-compliant client and enter the setup code presented on the generating device.");
 		case MR_STR_SELFTALK_SUBTITLE:     return safe_strdup("Messages I sent to myself");
+		case MR_STR_CANTDECRYPT_MSG_BODY:  return safe_strdup("This message was encrypted for another setup.");
 	}
 	return safe_strdup("ErrStr");
 }

+ 3 - 5
deltachat-ios/libraries/deltachat-core/src/mrstock.h

@@ -56,17 +56,15 @@ extern "C" {
 #define MR_STR_NONETWORK                  22
 #define MR_STR_GIF                        23
 #define MR_STR_ENCRYPTEDMSG               24
-#define MR_STR_ENCR_E2E                   25
+#define MR_STR_E2E_AVAILABLE              25
 #define MR_STR_ENCR_TRANSP                27
 #define MR_STR_ENCR_NONE                  28
+#define MR_STR_CANTDECRYPT_MSG_BODY       29
 #define MR_STR_FINGERPRINTS               30
 #define MR_STR_READRCPT                   31
 #define MR_STR_READRCPT_MAILBODY          32
 #define MR_STR_MSGGRPIMGDELETED           33
-#define MR_STR_E2E_FINE                   34
-#define MR_STR_E2E_NO_AUTOCRYPT           35
-#define MR_STR_E2E_DIS_BY_YOU             36
-#define MR_STR_E2E_DIS_BY_RCPT            37
+#define MR_STR_E2E_PREFERRED              34
 #define MR_STR_ARCHIVEDCHATS              40
 #define MR_STR_STARREDMSGS                41
 #define MR_STR_AC_SETUP_MSG_SUBJECT       42

+ 7 - 3
deltachat-ios/libraries/deltachat-core/src/mrtools.c

@@ -179,6 +179,10 @@ int mr_str_replace(char** haystack, const char* needle, const char* replacement)
 int mr_str_contains(const char* haystack, const const char* needle)
 {
 	/* case-insensitive search of needle in haystack, return 1 if found, 0 if not */
+	if( haystack==NULL || needle == NULL ) {
+		return 0;
+	}
+
 	if( strstr(haystack, needle)!=NULL ) {
 		return 1;
 	}
@@ -359,7 +363,7 @@ void mr_truncate_n_unwrap_str(char* buf, int approx_characters, int do_unwrap)
 	/* Function unwraps the given string and removes unnecessary whitespace.
 	Function stops processing after approx_characters are processed.
 	(as we're using UTF-8, for simplicity, we cut the string only at whitespaces). */
-	const char* ellipse_utf8 = do_unwrap? " ..." : " " MR_ELLIPSE_STR; /* a single line is truncated `...` instead of `[...]` (the former is typically also used by the UI to fit strings in a rectangle) */
+	const char* ellipse_utf8 = do_unwrap? " ..." : " " MR_EDITORIAL_ELLIPSE; /* a single line is truncated `...` instead of `[...]` (the former is typically also used by the UI to fit strings in a rectangle) */
 	int lastIsCharacter = 0;
 	unsigned char* p1 = (unsigned char*)buf; /* force unsigned - otherwise the `> ' '` comparison will fail */
 	while( *p1 ) {
@@ -399,7 +403,7 @@ void mr_truncate_n_unwrap_str(char* buf, int approx_characters, int do_unwrap)
 
 void mr_truncate_str(char* buf, int approx_chars)
 {
-	if( approx_chars > 0 && strlen(buf) > approx_chars+strlen(MR_ELLIPSE_STR) )
+	if( approx_chars > 0 && strlen(buf) > approx_chars+strlen(MR_EDITORIAL_ELLIPSE) )
 	{
 		char* p = &buf[approx_chars]; /* null-terminate string at the desired length */
 		*p = 0;
@@ -411,7 +415,7 @@ void mr_truncate_str(char* buf, int approx_chars)
 			}
 		}
 
-		strcat(p, MR_ELLIPSE_STR);
+		strcat(p, MR_EDITORIAL_ELLIPSE);
 	}
 }
 

+ 3 - 1
deltachat-ios/libraries/deltachat-core/src/mrtools.h

@@ -37,7 +37,9 @@ extern "C" {
 int     mr_exactly_one_bit_set     (int v);
 
 /* string tools */
-#define MR_ELLIPSE_STR             "[...]"
+#define MR_EDITORIAL_OPEN          "["
+#define MR_EDITORIAL_CLOSE         "]"
+#define MR_EDITORIAL_ELLIPSE       MR_EDITORIAL_OPEN "..." MR_EDITORIAL_CLOSE
 char*   safe_strdup                (const char*); /* safe_strdup() returns empty string if NULL is given, never returns NULL (exists on errors) */
 char*   strdup_keep_null           (const char*); /* strdup(NULL) is undefined, safe_strdup_keep_null(NULL) returns NULL in this case */
 int     atoi_null_is_0             (const char*);

+ 66 - 0
deltachat-ios/libraries/deltachat-core/src/mruudecode.c

@@ -0,0 +1,66 @@
+/*******************************************************************************
+ *
+ *                              Delta Chat Core
+ *                      Copyright (C) 2017 Björn Petersen
+ *                   Contact: r10s@b44t.com, http://b44t.com
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ ******************************************************************************/
+
+
+#include "mrmailbox_internal.h"
+#include "mruudecode.h"
+
+
+/**
+ * This function takes a text and returns this text stripped by the first uuencoded part;
+ * the uuencoded part itself is returned by three return parameters.
+ *
+ * If there are no uuencoded parts, the function terminates fast by returning NULL.
+ *
+ * @param text Null-terminated text to search uuencode parts in.
+ *     The text is not modified, instead, the modified text is returned on success.
+ *
+ * @param[out] ret_binary Points to a pointer that is set to the binary blob on
+ *     success.
+ *     The data is allocated with malloc() and must be free()'d by the caller.
+ *     If no uuencoded part is found, this parameter is set to NULL and the function returns NULL.
+ *
+ * @param[out] ret_binary_bytes Points to an integer that should be set to
+ *     binary blob bytes on success.
+ *
+ * @param[out] ret_filename Points to a pointer that should be set to the filename of the blob.
+ *     The data is allocated with malloc() and must be free()'d by the caller.
+ *     If no uuencoded part is found, this parameter is set to NULL and the function returns NULL.
+ *
+ * @return If uuencoded parts are found in the given text, the function returns the
+ *     given text stripped by the first uuencode block.
+ *     The caller will call mruudecode_do() again with this remaining text then.
+ *     This way, multiple uuencoded parts can be stripped from a text.
+ *     If no uuencoded parts are found or on errors, NULL is returned.
+ */
+char* mruudecode_do(const char* text, char** ret_binary, size_t* ret_binary_bytes, char** ret_filename)
+{
+	// CAVE: This function may be called in a loop until it returns NULL, so make sure not to create an invinitive look.
+
+	if( text == NULL || ret_binary == NULL || ret_binary_bytes == NULL || ret_filename == NULL ) {
+		goto cleanup; // bad parameters
+	}
+
+cleanup:
+	return NULL;
+}
+
+

+ 37 - 0
deltachat-ios/libraries/deltachat-core/src/mruudecode.h

@@ -0,0 +1,37 @@
+/*******************************************************************************
+ *
+ *                              Delta Chat Core
+ *                      Copyright (C) 2017 Björn Petersen
+ *                   Contact: r10s@b44t.com, http://b44t.com
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see http://www.gnu.org/licenses/ .
+ *
+ ******************************************************************************/
+
+
+#ifndef __MRUUDECODE_H__
+#define __MRUUDECODE_H__
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+char* mruudecode_do(const char* text, char** ret_binary, size_t* ret_binary_bytes, char** ret_filename);
+
+
+#ifdef __cplusplus
+} /* /extern "C" */
+#endif
+#endif /* __MRUUDECODE_H__ */
+