Alla Reinsch 7 anos atrás
pai
commit
fcf3af9884
38 arquivos alterados com 958 adições e 509 exclusões
  1. 2 2
      deltachat-ios/libraries/deltachat-core/README.md
  2. 9 7
      deltachat-ios/libraries/deltachat-core/cmdline/cmdline.c
  3. 3 2
      deltachat-ios/libraries/deltachat-core/cmdline/main.c
  4. 3 3
      deltachat-ios/libraries/deltachat-core/cmdline/stress.c
  5. 7 1
      deltachat-ios/libraries/deltachat-core/deltachat-core.cbp
  6. 3 1
      deltachat-ios/libraries/deltachat-core/src/meson.build
  7. 85 7
      deltachat-ios/libraries/deltachat-core/src/mrapeerstate.c
  8. 11 1
      deltachat-ios/libraries/deltachat-core/src/mrapeerstate.h
  9. 46 0
      deltachat-ios/libraries/deltachat-core/src/mrchat.c
  10. 1 0
      deltachat-ios/libraries/deltachat-core/src/mrchat.h
  11. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrchatlist.c
  12. 4 3
      deltachat-ios/libraries/deltachat-core/src/mrcontact-private.h
  13. 51 3
      deltachat-ios/libraries/deltachat-core/src/mrcontact.c
  14. 3 2
      deltachat-ios/libraries/deltachat-core/src/mrcontact.h
  15. 3 2
      deltachat-ios/libraries/deltachat-core/src/mrevent.h
  16. 3 2
      deltachat-ios/libraries/deltachat-core/src/mrjob.c
  17. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrjob.h
  18. 13 7
      deltachat-ios/libraries/deltachat-core/src/mrmailbox-private.h
  19. 130 83
      deltachat-ios/libraries/deltachat-core/src/mrmailbox.c
  20. 3 3
      deltachat-ios/libraries/deltachat-core/src/mrmailbox.h
  21. 18 5
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_e2ee.c
  22. 4 3
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_imex.c
  23. 29 0
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_keyhistory.c
  24. 2 8
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_log.c
  25. 260 0
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_qr.c
  26. 32 8
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_receive_imf.c
  27. 138 276
      deltachat-ios/libraries/deltachat-core/src/mrmailbox_securejoin.c
  28. 28 31
      deltachat-ios/libraries/deltachat-core/src/mrmimefactory.c
  29. 7 7
      deltachat-ios/libraries/deltachat-core/src/mrmimefactory.h
  30. 3 3
      deltachat-ios/libraries/deltachat-core/src/mrmimeparser.c
  31. 3 0
      deltachat-ios/libraries/deltachat-core/src/mrmimeparser.h
  32. 21 14
      deltachat-ios/libraries/deltachat-core/src/mrmsg.c
  33. 1 1
      deltachat-ios/libraries/deltachat-core/src/mrmsg.h
  34. 6 5
      deltachat-ios/libraries/deltachat-core/src/mrparam.h
  35. 23 15
      deltachat-ios/libraries/deltachat-core/src/mrsqlite3.c
  36. 1 0
      deltachat-ios/libraries/deltachat-core/src/mrsqlite3.h
  37. 0 1
      deltachat-ios/libraries/deltachat-core/src/mrstock.c
  38. 0 1
      deltachat-ios/libraries/deltachat-core/src/mrstock.h

+ 2 - 2
deltachat-ios/libraries/deltachat-core/README.md

@@ -68,7 +68,7 @@ these to work.
 
 - [bzip2](http://bzip.org)
 
-To build you need to have [meson](http://mesonbuild.com) and
+To build you need to have [meson](http://mesonbuild.com) (at least version 0.36.0) and
 [ninja](https://ninja-build.org) installed as well.
 
 On Linux (e.g. Debian Stretch) you can install all these using: 
@@ -84,7 +84,7 @@ cd builddir
 meson
 # optionally configure some parameters
 # run `meson configure` to see the options, e.g. 
-meson config -Dlibdir=lib
+meson configure -Dlibdir=lib
 ninja
 sudo ninja install
 sudo ldconfig

+ 9 - 7
deltachat-ios/libraries/deltachat-core/cmdline/cmdline.c

@@ -139,7 +139,7 @@ static int poke_public_key(mrmailbox_t* mailbox, const char* addr, const char* p
 	/* mainly for testing: if the partner does not support Autocrypt,
 	encryption is disabled as soon as the first messages comes from the partner */
 	mraheader_t*    header = mraheader_new();
-	mrapeerstate_t* peerstate = mrapeerstate_new();
+	mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
 	int             locked = 0, success = 0;
 
 	if( addr==NULL || public_key_file==NULL || peerstate==NULL || header==NULL ) {
@@ -318,7 +318,7 @@ static void log_msglist(mrmailbox_t* mailbox, mrarray_t* msglist)
 					msgtext,
 					mrmsg_is_starred(msg)? " \xE2\x98\x85" : "",
 					mrmsg_get_from_id(msg)==1? "" : (mrmsg_get_state(msg)==MR_STATE_IN_SEEN? "[SEEN]" : (mrmsg_get_state(msg)==MR_STATE_IN_NOTICED? "[NOTICED]":"[FRESH]")),
-					mrmsg_is_systemcmd(msg)? "[SYSTEM]" : "",
+					mrmsg_is_info(msg)? "[INFO]" : "",
 					statestr,
 					temp2);
 			free(msgtext);
@@ -338,7 +338,7 @@ static void log_contactlist(mrmailbox_t* mailbox, mrarray_t* contacts)
 {
 	int             i, cnt = mrarray_get_cnt(contacts);
 	mrcontact_t*    contact = NULL;
-	mrapeerstate_t* peerstate = mrapeerstate_new();
+	mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
 
 	for( i = 0; i < cnt; i++ ) {
 		uint32_t contact_id = mrarray_get_id(contacts, i);
@@ -347,7 +347,8 @@ 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);
-			line = mr_mprintf("%s, %s", (name&&name[0])? name : "<name unset>", (addr&&addr[0])? addr : "<addr unset>");
+			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");
 			mrsqlite3_lock(mailbox->m_sql);
 				int peerstate_ok = mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, addr);
 			mrsqlite3_unlock(mailbox->m_sql);
@@ -703,8 +704,9 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 
 					char* temp_subtitle = mrchat_get_subtitle(chat);
 					char* temp_name = mrchat_get_name(chat);
-						mrmailbox_log_info(mailbox, 0, "%s#%i: %s [%s] [%i fresh]", mrchat_get_type(chat)==MR_CHAT_TYPE_GROUP? "Groupchat" : "Chat",
-							(int)mrchat_get_id(chat), temp_name, temp_subtitle, (int)mrmailbox_get_fresh_msg_count(mailbox, mrchat_get_id(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)));
 					free(temp_subtitle);
 					free(temp_name);
 
@@ -1166,7 +1168,7 @@ char* mrmailbox_cmdline(mrmailbox_t* mailbox, const char* cmdline)
 
 	else if( strcmp(cmd, "getqr")==0 )
 	{
-		ret = mrmailbox_oob_get_qr(mailbox);
+		ret = mrmailbox_get_securejoin_qr(mailbox, 0);
 	}
 	else if( strcmp(cmd, "checkqr")==0 )
 	{

+ 3 - 2
deltachat-ios/libraries/deltachat-core/cmdline/main.c

@@ -81,7 +81,7 @@ static uintptr_t receive_event(mrmailbox_t* mailbox, int event, uintptr_t data1,
 			{
 				char* ret = NULL;
 				char* tempFile = mr_get_fine_pathNfilename(mailbox->m_blobdir, "curl.result");
-				char* cmd = mr_mprintf("curl --silent --location --fail %s > %s", (char*)data1, tempFile); /* --location = follow redirects */
+				char* cmd = mr_mprintf("curl --silent --location --fail --insecure %s > %s", (char*)data1, tempFile); /* --location = follow redirects */
 				int error = system(cmd);
 				if( error == 0 ) { /* -1=system() error, !0=curl errors forced by -f, 0=curl success */
 					size_t bytes = 0;
@@ -159,7 +159,8 @@ int main(int argc, char ** argv)
 		}
 		else if( strcmp(cmd, "getqr")==0 || strcmp(cmd, "getbadqr")==0 )
 		{
-			char* qrstr  = mrmailbox_oob_get_qr(mailbox);
+			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'; }
 			}

+ 3 - 3
deltachat-ios/libraries/deltachat-core/cmdline/stress.c

@@ -356,7 +356,7 @@ void stress_functions(mrmailbox_t* mailbox)
 		assert( strcmp("self="         MR_STRINGIFY(MR_CONTACT_ID_SELF),          "self=1")==0 );
 		assert( strcmp("spcl_contact=" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL),  "spcl_contact=9")==0 );
 
-		assert( strcmp("grpimg="    MR_STRINGIFY(MR_SYSTEM_GROUPIMAGE_CHANGED), "grpimg=3")==0 );
+		assert( strcmp("grpimg="    MR_STRINGIFY(MR_CMD_GROUPIMAGE_CHANGED), "grpimg=3")==0 );
 
 		assert( MRP_FILE == 'f' );
 		assert( MRP_WIDTH == 'w' );
@@ -771,12 +771,12 @@ void stress_functions(mrmailbox_t* mailbox)
 
 	if( mrmailbox_is_configured(mailbox) )
 	{
-		char* qr = mrmailbox_oob_get_qr(mailbox);
+		char* qr = mrmailbox_get_securejoin_qr(mailbox, 0);
 		assert( strlen(qr)>55 && strncmp(qr, "OPENPGP4FPR:", 12)==0 && strncmp(&qr[52], "#v=", 3)==0 );
 
 		mrlot_t* res = mrmailbox_check_qr(mailbox, qr);
 		assert( res );
-		assert( res->m_state == MR_QR_FPR_ASK_OOB || res->m_state == MR_QR_FPR_MISMATCH || res->m_state == MR_QR_FPR_WITHOUT_ADDR );
+		assert( res->m_state == MR_QR_ASK_SECUREJOIN || res->m_state == MR_QR_FPR_MISMATCH || res->m_state == MR_QR_FPR_WITHOUT_ADDR );
 
 		mrlot_unref(res);
 		free(qr);

+ 7 - 1
deltachat-ios/libraries/deltachat-core/deltachat-core.cbp

@@ -489,15 +489,21 @@
 			<Option compilerVar="CC" />
 		</Unit>
 		<Unit filename="src/mrmailbox_internal.h" />
+		<Unit filename="src/mrmailbox_keyhistory.c">
+			<Option compilerVar="CC" />
+		</Unit>
 		<Unit filename="src/mrmailbox_log.c">
 			<Option compilerVar="CC" />
 		</Unit>
-		<Unit filename="src/mrmailbox_oob.c">
+		<Unit filename="src/mrmailbox_qr.c">
 			<Option compilerVar="CC" />
 		</Unit>
 		<Unit filename="src/mrmailbox_receive_imf.c">
 			<Option compilerVar="CC" />
 		</Unit>
+		<Unit filename="src/mrmailbox_securejoin.c">
+			<Option compilerVar="CC" />
+		</Unit>
 		<Unit filename="src/mrmimefactory.c">
 			<Option compilerVar="CC" />
 		</Unit>

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

@@ -17,9 +17,11 @@ lib_src = [
   'mrmailbox_configure.c',
   'mrmailbox_e2ee.c',
   'mrmailbox_imex.c',
+  'mrmailbox_keyhistory.c',
   'mrmailbox_log.c',
-  'mrmailbox_oob.c',
+  'mrmailbox_qr.c',
   'mrmailbox_receive_imf.c',
+  'mrmailbox_securejoin.c',
   'mrmimefactory.c',
   'mrmimeparser.c',
   'mrmsg.c',

+ 85 - 7
deltachat-ios/libraries/deltachat-core/src/mrapeerstate.c

@@ -46,6 +46,7 @@ static void mrapeerstate_empty(mrapeerstate_t* ths)
 
 	free(ths->m_fingerprint);
 	ths->m_fingerprint = NULL;
+	ths->m_verified    = 0;
 
 	if( ths->m_public_key ) {
 		mrkey_unref(ths->m_public_key);
@@ -58,12 +59,14 @@ static void mrapeerstate_empty(mrapeerstate_t* ths)
 		mrkey_unref(ths->m_gossip_key);
 		ths->m_gossip_key = NULL;
 	}
+
+	ths->m_degrade_event = 0;
 }
 
 
 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"
+	#define PEERSTATE_FIELDS "addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, gossip_timestamp, gossip_key, fingerprint, 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);
@@ -72,6 +75,7 @@ static void mrapeerstate_set_from_stmt__(mrapeerstate_t* peerstate, sqlite3_stmt
 	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);
 
 	if( sqlite3_column_type(stmt, PUBLIC_KEY_COL)!=SQLITE_NULL ) {
 		peerstate->m_public_key = mrkey_new();
@@ -161,7 +165,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=? "
+			"       public_key=?, gossip_timestamp=?, gossip_key=?, fingerprint=?, verified=? "
 			" WHERE addr=?;");
 		sqlite3_bind_int64(stmt, 1, ths->m_last_seen);
 		sqlite3_bind_int64(stmt, 2, ths->m_last_seen_autocrypt);
@@ -170,7 +174,8 @@ int mrapeerstate_save_to_db__(const mrapeerstate_t* ths, mrsqlite3_t* sql, int c
 		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_text (stmt, 8, ths->m_addr, -1, SQLITE_STATIC);
+		sqlite3_bind_int  (stmt, 8, ths->m_verified);
+		sqlite3_bind_text (stmt, 9, ths->m_addr, -1, SQLITE_STATIC);
 		if( sqlite3_step(stmt) != SQLITE_DONE ) {
 			goto cleanup;
 		}
@@ -200,7 +205,7 @@ cleanup:
  ******************************************************************************/
 
 
-mrapeerstate_t* mrapeerstate_new()
+mrapeerstate_t* mrapeerstate_new(mrmailbox_t* mailbox)
 {
 	mrapeerstate_t* ths = NULL;
 
@@ -208,6 +213,8 @@ mrapeerstate_t* mrapeerstate_new()
 		exit(43); /* cannot allocate little memory, unrecoverable error */
 	}
 
+	ths->m_mailbox = mailbox;
+
 	return ths;
 }
 
@@ -348,9 +355,14 @@ int mrapeerstate_degrade_encryption(mrapeerstate_t* ths, time_t message_time)
 		return 0;
 	}
 
+	if( ths->m_prefer_encrypt == MRA_PE_MUTUAL ) {
+		ths->m_degrade_event |= MRA_DE_ENCRYPTION_PAUSED;
+	}
+
 	ths->m_prefer_encrypt = MRA_PE_RESET;
 	ths->m_last_seen      = message_time; /*last_seen_autocrypt is not updated as there was not Autocrypt:-header seen*/
 	ths->m_to_save        = MRA_SAVE_ALL;
+
 	return 1;
 }
 
@@ -373,6 +385,10 @@ void mrapeerstate_apply_header(mrapeerstate_t* ths, const mraheader_t* header, t
 		if( (header->m_prefer_encrypt==MRA_PE_MUTUAL || header->m_prefer_encrypt==MRA_PE_NOPREFERENCE) /*this also switches from MRA_PE_RESET to MRA_PE_NOPREFERENCE, which is just fine as the function is only called _if_ the Autocrypt:-header is preset at all */
 		 &&  header->m_prefer_encrypt != ths->m_prefer_encrypt )
 		{
+			if( ths->m_prefer_encrypt == MRA_PE_MUTUAL && header->m_prefer_encrypt != MRA_PE_MUTUAL ) {
+				ths->m_degrade_event |= MRA_DE_ENCRYPTION_PAUSED;
+			}
+
 			ths->m_prefer_encrypt = header->m_prefer_encrypt;
 			ths->m_to_save |= MRA_SAVE_ALL;
 		}
@@ -421,7 +437,9 @@ 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)
+ * (public_key, if set, gossip_key otherwise).
+ *
+ * If the fingerprint has changed, the verified-state is reset.
  *
  * An explicit call to this function from outside this class is only needed
  * for database updates; the mrapeerstate_init_*() and mrapeerstate_apply_*()
@@ -431,6 +449,7 @@ int mrapeerstate_recalc_fingerprint(mrapeerstate_t* peerstate)
 {
 	int            success = 0;
 	const mrkey_t* key = NULL;
+	char*          old_fingerprint = NULL;
 
 	if( peerstate == NULL ) {
 		goto cleanup;
@@ -440,12 +459,71 @@ int mrapeerstate_recalc_fingerprint(mrapeerstate_t* peerstate)
 		goto cleanup;
 	}
 
-	free(peerstate->m_fingerprint);
+	old_fingerprint = peerstate->m_fingerprint;
+
 	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 */
-	peerstate->m_to_save |= MRA_SAVE_ALL;
+
+	if( old_fingerprint == NULL
+	 || old_fingerprint[0] == 0
+	 || peerstate->m_fingerprint == NULL
+	 || peerstate->m_fingerprint[0] == 0
+	 || strcasecmp(old_fingerprint, peerstate->m_fingerprint) != 0 )
+	{
+		peerstate->m_to_save  |= MRA_SAVE_ALL;
+		peerstate->m_verified = 0;
+
+		if( old_fingerprint && old_fingerprint[0] ) { // no degrade event when we recveive just the initial fingerprint
+			peerstate->m_degrade_event |= MRA_DE_FINGERPRINT_CHANGED;
+		}
+	}
 
 	success = 1;
 
 cleanup:
+	free(old_fingerprint);
 	return success;
 }
+
+
+/**
+ * If the fingerprint of the peerstate equals the given fingerprint, the
+ * peerstate is marked as being verified.
+ *
+ * The given fingerprint is present only to ensure the peer has not changed
+ * between fingerprint comparison and calling this function.
+ *
+ * @memberof mrapeerstate_t
+ *
+ * @param peerstate The peerstate object.
+ * @param fingerprint Fingerprint expected in the object
+ *
+ * @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__()
+ *     to permanently store this state.
+ *     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 verified = 0;
+
+	if( peerstate == NULL || fingerprint == NULL ) {
+		goto cleanup;
+	}
+
+	if( peerstate->m_fingerprint ==  NULL
+	 || peerstate->m_fingerprint[0] == 0
+	 || fingerprint[0] == 0
+	 || strcasecmp(peerstate->m_fingerprint, fingerprint) != 0 )
+	{
+		goto cleanup;
+	}
+
+	peerstate->m_to_save        |= MRA_SAVE_ALL;
+	peerstate->m_prefer_encrypt =  MRA_PE_MUTUAL;
+	peerstate->m_verified       = 1;
+	verified                    = 1;
+
+cleanup:
+	return verified;
+}

+ 11 - 1
deltachat-ios/libraries/deltachat-core/src/mrapeerstate.h

@@ -44,6 +44,8 @@ typedef struct mraheader_t mraheader_t;
 typedef struct mrapeerstate_t
 {
 	/** @privatesection */
+	mrmailbox_t*   m_mailbox;
+
 	char*          m_addr;
 	time_t         m_last_seen;  /* may be 0 if the peer was created by gossipping */
 
@@ -55,14 +57,20 @@ typedef struct mrapeerstate_t
 	mrkey_t*       m_gossip_key; /* may be NULL */
 
 	char*          m_fingerprint; /* fingerprint belonging to public_key (if set) or m_gossip_key (otherwise), may be NULL */
+	int            m_verified;    // fingerprint verified?
 
 	#define        MRA_SAVE_TIMESTAMPS 0x01
 	#define        MRA_SAVE_ALL        0x02
 	int            m_to_save;
+
+	#define        MRA_DE_ENCRYPTION_PAUSED   0x01 // recoverable by an incoming encrypted mail
+	#define        MRA_DE_FINGERPRINT_CHANGED 0x02 // recoverable by a new verify
+	int            m_degrade_event;
+
 } mrapeerstate_t;
 
 
-mrapeerstate_t* mrapeerstate_new                  (); /* the returned pointer is ref'd and must be unref'd after usage */
+mrapeerstate_t* mrapeerstate_new                  (mrmailbox_t*); /* the returned pointer is ref'd and must be unref'd after usage */
 void            mrapeerstate_unref                (mrapeerstate_t*);
 
 int             mrapeerstate_init_from_header     (mrapeerstate_t*, const mraheader_t*, time_t message_time);
@@ -79,6 +87,8 @@ mrkey_t*        mrapeerstate_peek_key             (const mrapeerstate_t*);
 
 int             mrapeerstate_recalc_fingerprint   (mrapeerstate_t*);
 
+int             mrapeerstate_set_verified         (mrapeerstate_t*, const char* fingerprint);
+
 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);

+ 46 - 0
deltachat-ios/libraries/deltachat-core/src/mrchat.c

@@ -378,6 +378,52 @@ int mrchat_is_unpromoted(mrchat_t* chat)
 }
 
 
+int mrchat_is_verified(mrchat_t* chat)
+{
+	// if you change the algorithm here, you may also want to adapt mrcontact_is_verfied()
+
+	int           chat_verified = 0;
+	int           locked = 0;
+	sqlite3_stmt* stmt;
+
+	if( chat == NULL || chat->m_magic != MR_CHAT_MAGIC ) {
+		goto cleanup;
+	}
+
+	if( chat->m_id == MR_CHAT_ID_DEADDROP || chat->m_id == MR_CHAT_ID_STARRED ) {
+		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 )
+		{
+			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
+			}
+		}
+
+		chat_verified = 1;
+
+cleanup:
+	if( locked ) { mrsqlite3_unlock(chat->m_mailbox->m_sql); }
+	return chat_verified;
+}
+
+
 /**
  * Check if a chat is a self talk.  Self talks are normal chats with
  * the only contact MR_CONTACT_ID_SELF.

+ 1 - 0
deltachat-ios/libraries/deltachat-core/src/mrchat.h

@@ -65,6 +65,7 @@ time_t          mrchat_get_draft_timestamp  (mrchat_t*);
 int             mrchat_get_archived         (mrchat_t*);
 int             mrchat_is_unpromoted        (mrchat_t*);
 int             mrchat_is_self_talk         (mrchat_t*);
+int             mrchat_is_verified          (mrchat_t*);
 
 
 #ifdef __cplusplus

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

@@ -233,7 +233,7 @@ mrlot_t* mrchatlist_get_summary(mrchatlist_t* chatlist, size_t index, mrchat_t*
 
 			if( lastmsg->m_from_id != MR_CONTACT_ID_SELF  &&  chat->m_type == MR_CHAT_TYPE_GROUP )
 			{
-				lastcontact = mrcontact_new();
+				lastcontact = mrcontact_new(chatlist->m_mailbox);
 				mrcontact_load_from_db__(lastcontact, chatlist->m_mailbox->m_sql, lastmsg->m_from_id);
 			}
 

+ 4 - 3
deltachat-ios/libraries/deltachat-core/src/mrcontact-private.h

@@ -36,6 +36,7 @@ struct _mrcontact
 	/** @privatesection */
 
 	uint32_t        m_magic;
+	mrmailbox_t*    m_mailbox;
 
 	/**
 	 * The contact ID.
@@ -68,9 +69,9 @@ struct _mrcontact
 #define MR_ORIGIN_OUTGOING_TO              0x4000 /* message sent by us */
 #define MR_ORIGIN_INTERNAL                0x40000 /* internal use */
 #define MR_ORIGIN_ADRESS_BOOK             0x80000 /* address is in our address book */
-#define MR_ORIGIN_MANUALLY_CREATED       0x100000 /* contact added by mrmailbox_create_contact() */
-#define MR_ORIGIN_SECURE_INVITED        0x1000000 /* set on Alice's side for contacts like Bob that have scanned the QR code offered by her */
-#define MR_ORIGIN_SECURE_JOINED         0x2000000 /* set on Bob's side for contacts scanned and verified from a QR code */
+#define MR_ORIGIN_SECUREJOIN_INVITED    0x1000000 /* set on Alice's side for contacts like Bob that have scanned the QR code offered by her. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling mrcontact_is_verfied() ! */
+#define MR_ORIGIN_SECUREJOIN_JOINED     0x2000000 /* set on Bob's side for contacts scanned and verified from a QR code. Only means the contact has once been established using the "securejoin" procedure in the past, getting the current key verification status requires calling mrcontact_is_verfied() ! */
+#define MR_ORIGIN_MANUALLY_CREATED      0x4000000 /* contact added mannually by mrmailbox_create_contact(), this should be the largets origin as otherwise the user cannot modify the names */
 
 #define MR_ORIGIN_MIN_CONTACT_LIST    (MR_ORIGIN_INCOMING_REPLY_TO) /* contacts with at least this origin value are shown in the contact list */
 #define MR_ORIGIN_MIN_VERIFIED        (MR_ORIGIN_INCOMING_REPLY_TO) /* contacts with at least this origin value are verified and known not to be spam */

+ 51 - 3
deltachat-ios/libraries/deltachat-core/src/mrcontact.c

@@ -22,6 +22,7 @@
 
 #include "mrmailbox_internal.h"
 #include "mrcontact.h"
+#include "mrapeerstate.h"
 
 #define MR_CONTACT_MAGIC 0x0c047ac7
 
@@ -35,7 +36,7 @@
  *
  * @return The contact object. Must be freed using mrcontact_unref() when done.
  */
-mrcontact_t* mrcontact_new()
+mrcontact_t* mrcontact_new(mrmailbox_t* mailbox)
 {
 	mrcontact_t* ths = NULL;
 
@@ -43,7 +44,8 @@ mrcontact_t* mrcontact_new()
 		exit(19); /* cannot allocate little memory, unrecoverable error */
 	}
 
-	ths->m_magic = MR_CONTACT_MAGIC;
+	ths->m_magic   = MR_CONTACT_MAGIC;
+	ths->m_mailbox = mailbox;
 
 	return ths;
 }
@@ -247,6 +249,50 @@ int mrcontact_is_blocked(mrcontact_t* contact)
 }
 
 
+/**
+ * Check if a contact was verified eg. by a secure-join QR code scan
+ * and if the key has not changed since this verification.
+ *
+ * The UI may draw a checkbox or sth. like that beside verified contacts.
+ *
+ * @memberof mrcontact_t
+ *
+ * @param contact The contact object.
+ *
+ * @return 1=contact is verified, 0=contact is not verified.
+ */
+int mrcontact_is_verified(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);
+
+	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
+	}
+
+	if( !mrapeerstate_load_by_addr__(peerstate, contact->m_mailbox->m_sql, contact->m_addr) ) {
+		goto cleanup;
+	}
+
+	if( peerstate->m_verified==0 || peerstate->m_prefer_encrypt!=MRA_PE_MUTUAL ) {
+		goto cleanup;
+	}
+
+	contact_verified = 1;
+
+cleanup:
+	mrapeerstate_unref(peerstate);
+	return contact_verified;
+}
+
+
 /**
  * Get the first name.
  *
@@ -390,7 +436,9 @@ int mrcontact_load_from_db__(mrcontact_t* ths, mrsqlite3_t* sql, uint32_t contac
 	else
 	{
 		stmt = mrsqlite3_predefine__(sql, SELECT_naob_FROM_contacts_i,
-			"SELECT name, addr, origin, blocked, authname FROM contacts WHERE id=?;");
+			"SELECT c.name, c.addr, c.origin, c.blocked, c.authname "
+			" FROM contacts c "
+			" WHERE c.id=?;");
 		sqlite3_bind_int(stmt, 1, contact_id);
 		if( sqlite3_step(stmt) != SQLITE_ROW ) {
 			goto cleanup;

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

@@ -37,11 +37,11 @@ extern "C" {
 typedef struct _mrcontact mrcontact_t;
 
 #define         MR_CONTACT_ID_SELF         1
-#define         MR_CONTACT_ID_SYSTEM       2
+#define         MR_CONTACT_ID_DEVICE       2
 #define         MR_CONTACT_ID_LAST_SPECIAL 9
 
 
-mrcontact_t* mrcontact_new                    (); /* the returned pointer is ref'd and must be unref'd after usage */
+mrcontact_t* mrcontact_new                    (mrmailbox_t*); /* the returned pointer is ref'd and must be unref'd after usage */
 void         mrcontact_empty                  (mrcontact_t*);
 void         mrcontact_unref                  (mrcontact_t*);
 
@@ -51,6 +51,7 @@ 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*);
 
 
 #ifdef __cplusplus

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

@@ -150,7 +150,8 @@ extern "C" {
 
 
 /**
- * Group changed.  The name or the image of a chat group was changed or members were added or removed.
+ * Chat changed.  The name or the image of a chat group was changed or members were added or removed.
+ * Or the verify state of a chat has changed.
  * See mrmailbox_set_chat_name(), mrmailbox_set_chat_profile_image(), mrmailbox_add_contact_to_chat()
  * and mrmailbox_remove_contact_from_chat().
  *
@@ -215,7 +216,7 @@ extern "C" {
 #define MR_EVENT_IMEX_FILE_WRITTEN        2052
 
 
-#define MR_EVENT_SECURE_JOIN_REQUESTED    2060
+#define MR_EVENT_SECUREJOIN_REQUESTED     2060
 
 
 /*******************************************************************************

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

@@ -198,18 +198,19 @@ void mrjob_exit_thread(mrmailbox_t* mailbox)
 }
 
 
-uint32_t mrjob_add__(mrmailbox_t* mailbox, int action, int foreign_id, const char* param)
+uint32_t mrjob_add__(mrmailbox_t* mailbox, int action, int foreign_id, const char* param, int delay_seconds)
 {
 	time_t        timestamp = time(NULL);
 	sqlite3_stmt* stmt;
 	uint32_t      job_id = 0;
 
 	stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_jobs_aafp,
-		"INSERT INTO jobs (added_timestamp, action, foreign_id, param) VALUES (?,?,?,?);");
+		"INSERT INTO jobs (added_timestamp, action, foreign_id, param, desired_timestamp) VALUES (?,?,?,?,?);");
 	sqlite3_bind_int64(stmt, 1, timestamp);
 	sqlite3_bind_int  (stmt, 2, action);
 	sqlite3_bind_int  (stmt, 3, foreign_id);
 	sqlite3_bind_text (stmt, 4, param? param : "",  -1, SQLITE_STATIC);
+	sqlite3_bind_int64(stmt, 5, delay_seconds>0? (timestamp+delay_seconds) : 0);
 	if( sqlite3_step(stmt) != SQLITE_DONE ) {
 		return 0;
 	}

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

@@ -51,7 +51,7 @@ typedef struct mrjob_t
 
 void     mrjob_init_thread     (mrmailbox_t*);
 void     mrjob_exit_thread     (mrmailbox_t*);
-uint32_t mrjob_add__           (mrmailbox_t*, int action, int foreign_id, const char* param); /* returns the job_id or 0 on errors. the job may or may not be done if the function returns. */
+uint32_t mrjob_add__           (mrmailbox_t*, int action, int foreign_id, const char* param, int delay); /* returns the job_id or 0 on errors. the job may or may not be done if the function returns. */
 void     mrjob_kill_action__   (mrmailbox_t*, int action); /* delete all pending jobs with the given action */
 
 #define  MR_AT_ONCE            0

+ 13 - 7
deltachat-ios/libraries/deltachat-core/src/mrmailbox-private.h

@@ -118,7 +118,7 @@ void            mrmailbox_set_group_explicitly_left__             (mrmailbox_t*,
 size_t          mrmailbox_get_real_msg_cnt__                      (mrmailbox_t*); /* the number of messages assigned to real chat (!=deaddrop, !=trash) */
 size_t          mrmailbox_get_deaddrop_msg_cnt__                  (mrmailbox_t*);
 int             mrmailbox_rfc724_mid_cnt__                        (mrmailbox_t*, const char* rfc724_mid);
-int             mrmailbox_rfc724_mid_exists__                     (mrmailbox_t*, const char* rfc724_mid, char** ret_server_folder, uint32_t* ret_server_uid);
+uint32_t        mrmailbox_rfc724_mid_exists__                     (mrmailbox_t*, const char* rfc724_mid, char** ret_server_folder, uint32_t* ret_server_uid);
 void            mrmailbox_update_server_uid__                     (mrmailbox_t*, const char* rfc724_mid, const char* server_folder, uint32_t server_uid);
 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);
@@ -128,6 +128,8 @@ void            mrmailbox_send_mdn                                (mrmailbox_t*,
 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);
 
 
 /* library private: end-to-end-encryption */
@@ -139,8 +141,8 @@ typedef struct mrmailbox_e2ee_helper_t {
 	void* m_cdata_to_free;
 } mrmailbox_e2ee_helper_t;
 
-void            mrmailbox_e2ee_encrypt      (mrmailbox_t*, const clist* recipients_addr, int e2ee_guaranteed, int encrypt_to_self, struct mailmime* in_out_message, mrmailbox_e2ee_helper_t*);
-int             mrmailbox_e2ee_decrypt      (mrmailbox_t*, struct mailmime* in_out_message, int* ret_validation_errors); /* returns 1 if sth. was decrypted, 0 in other cases */
+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_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*);
@@ -153,11 +155,15 @@ int             mrmailbox_alloc_ongoing     (mrmailbox_t*);
 void            mrmailbox_free_ongoing      (mrmailbox_t*);
 
 
-/* private oob-stuff */
-int             mrmailbox_oob_is_handshake_message__  (mrmailbox_t*, mrmimeparser_t*); /* must be called from lock */
-void            mrmailbox_oob_handle_handshake_message(mrmailbox_t*, mrmimeparser_t*, uint32_t chat_id); /* must not be called from lock */
+/* 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 */
 
-uint32_t        mrmailbox_add_system_msg              (mrmailbox_t*, uint32_t chat_id, const char* text);
+#define OPENPGP4FPR_SCHEME "OPENPGP4FPR:" /* yes: uppercase */
+
+
+/* library private: key-history */
+void            mrmailbox_add_to_keyhistory__(mrmailbox_t*, const char* rfc724_mid, time_t, const char* addr, const char* fingerprint);
 
 
 #ifdef __cplusplus

+ 130 - 83
deltachat-ios/libraries/deltachat-core/src/mrmailbox.c

@@ -758,7 +758,7 @@ void mrmailbox_connect(mrmailbox_t* mailbox)
 		mailbox->m_imap->m_log_connect_errors = 1;
 
 		mrjob_kill_action__(mailbox, MRJ_CONNECT_TO_IMAP);
-		mrjob_add__(mailbox, MRJ_CONNECT_TO_IMAP, 0, NULL);
+		mrjob_add__(mailbox, MRJ_CONNECT_TO_IMAP, 0, NULL, 0);
 
 	mrsqlite3_unlock(mailbox->m_sql);
 }
@@ -2075,7 +2075,7 @@ void mrmailbox_send_msg_to_imap(mrmailbox_t* mailbox, mrjob_t* job)
 		goto cleanup; /* should not happen as we've sent the message to the SMTP server before */
 	}
 
-	if( !mrmimefactory_render(&mimefactory, 1/*encrypt to self*/) ) {
+	if( !mrmimefactory_render(&mimefactory) ) {
 		goto cleanup; /* should not happen as we've sent the message to the SMTP server before */
 	}
 
@@ -2142,9 +2142,9 @@ void mrmailbox_send_msg_to_smtp(mrmailbox_t* mailbox, mrjob_t* job)
 		goto cleanup;
 	}
 
-	/* send message - it's okay if there are not recipients, this is a group with only OURSELF; we only upload to IMAP in this case */
+	/* send message - it's okay if there are no recipients, this is a group with only OURSELF; we only upload to IMAP in this case */
 	if( clist_count(mimefactory.m_recipients_addr) > 0 ) {
-		if( !mrmimefactory_render(&mimefactory, 0/*encrypt_to_self*/) ) {
+		if( !mrmimefactory_render(&mimefactory) ) {
 			mark_as_error(mailbox, mimefactory.m_msg);
 			mrmailbox_log_error(mailbox, 0, "Empty message."); /* should not happen */
 			goto cleanup; /* no redo, no IMAP - there won't be more recipients next time. */
@@ -2189,10 +2189,13 @@ void mrmailbox_send_msg_to_smtp(mrmailbox_t* mailbox, mrjob_t* job)
 
 		if( (mailbox->m_imap->m_server_flags&MR_NO_EXTRA_IMAP_UPLOAD)==0
 		 && mrparam_get(mimefactory.m_chat->m_param, MRP_SELFTALK, 0)==0
-		 && mrparam_get_int(mimefactory.m_msg->m_param, MRP_SYSTEM_CMD, 0)!=MR_SYSTEM_OOB_VERIFY_MESSAGE ) {
-			mrjob_add__(mailbox, MRJ_SEND_MSG_TO_IMAP, mimefactory.m_msg->m_id, NULL); /* send message to IMAP in another job */
+		 && mrparam_get_int(mimefactory.m_msg->m_param, MRP_CMD, 0)!=MR_CMD_SECUREJOIN_MESSAGE ) {
+			mrjob_add__(mailbox, MRJ_SEND_MSG_TO_IMAP, mimefactory.m_msg->m_id, NULL, 0); /* send message to IMAP in another job */
 		}
 
+		// TODO: add to keyhistory
+		mrmailbox_add_to_keyhistory__(mailbox, NULL, 0, NULL, NULL);
+
 	mrsqlite3_commit__(mailbox->m_sql);
 	mrsqlite3_unlock(mailbox->m_sql);
 
@@ -2267,8 +2270,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;
-	int system_command = mrparam_get_int(msg->m_param, MRP_SYSTEM_CMD, 0);
-	if( mailbox->m_e2ee_enabled && system_command!=MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE )
+	if( mailbox->m_e2ee_enabled && mrparam_get_int(msg->m_param, MRP_FORCE_UNENCRYPTED, 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,
@@ -2310,10 +2312,6 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 	if( do_guarantee_e2ee ) {
 		mrparam_set_int(msg->m_param, MRP_GUARANTEE_E2EE, 1);
 	}
-	else {
-		/* if we cannot guarantee E2EE, clear the flag (may be set if the message was loaded from the database, eg. for forwarding messages ) */
-		mrparam_set(msg->m_param, MRP_GUARANTEE_E2EE, NULL);
-	}
 	mrparam_set(msg->m_param, MRP_ERRONEOUS_E2EE, NULL); /* reset eg. on forwarding */
 
 	/* add message to the database */
@@ -2338,7 +2336,7 @@ static uint32_t mrmailbox_send_msg_i__(mrmailbox_t* mailbox, mrchat_t* chat, con
 
 	/* finalize message object on database, we set the chat ID late as we don't know it sooner */
 	mrmailbox_update_msg_chat_id__(mailbox, msg_id, chat->m_id);
-	mrjob_add__(mailbox, MRJ_SEND_MSG_TO_SMTP, msg_id, NULL); /* resuts on an asynchronous call to mrmailbox_send_msg_to_smtp()  */
+	mrjob_add__(mailbox, MRJ_SEND_MSG_TO_SMTP, msg_id, NULL, 0); /* resuts on an asynchronous call to mrmailbox_send_msg_to_smtp()  */
 
 cleanup:
 	free(rfc724_mid);
@@ -2817,16 +2815,43 @@ cleanup:
 }
 
 
+/* similar to mrmailbox_add_device_msg() but without locking and without sending
+ * an event.
+ */
+uint32_t mrmailbox_add_device_msg__(mrmailbox_t* mailbox, uint32_t chat_id, const char* text, time_t timestamp)
+{
+	sqlite3_stmt* stmt = NULL;
+
+	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || text == NULL ) {
+		return 0;
+	}
+
+	stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_cftttst,
+		"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt) VALUES (?,?,?, ?,?,?, ?);");
+	sqlite3_bind_int  (stmt,  1, chat_id);
+	sqlite3_bind_int  (stmt,  2, MR_CONTACT_ID_DEVICE);
+	sqlite3_bind_int  (stmt,  3, MR_CONTACT_ID_DEVICE);
+	sqlite3_bind_int64(stmt,  4, timestamp);
+	sqlite3_bind_int  (stmt,  5, MR_MSG_TEXT);
+	sqlite3_bind_int  (stmt,  6, MR_STATE_IN_NOTICED);
+	sqlite3_bind_text (stmt,  7, text,  -1, SQLITE_STATIC);
+	if( sqlite3_step(stmt) != SQLITE_DONE ) {
+		return 0;
+	}
+
+	return sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj);
+}
+
+
 /*
- * Log a system message.
- * Such a message is typically shown in the "middle" of the chat.
+ * Log a device message.
+ * Such a message is typically shown in the "middle" of the chat, the user can check this using mrmsg_is_info().
  * Texts are typically "Alice has added Bob to the group" or "Alice fingerprint verified."
  */
-uint32_t mrmailbox_add_system_msg(mrmailbox_t* mailbox, uint32_t chat_id, const char* text)
+uint32_t mrmailbox_add_device_msg(mrmailbox_t* mailbox, uint32_t chat_id, const char* text)
 {
 	uint32_t      msg_id = 0;
 	int           locked = 0;
-	sqlite3_stmt* stmt = NULL;
 
 	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || text == NULL ) {
 		goto cleanup;
@@ -2835,25 +2860,13 @@ uint32_t mrmailbox_add_system_msg(mrmailbox_t* mailbox, uint32_t chat_id, const
 	mrsqlite3_lock(mailbox->m_sql);
 	locked = 1;
 
-		stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_cftttst,
-			"INSERT INTO msgs (chat_id,from_id,to_id, timestamp,type,state, txt) VALUES (?,?,?, ?,?,?, ?);");
-		sqlite3_bind_int  (stmt,  1, chat_id);
-		sqlite3_bind_int  (stmt,  2, MR_CONTACT_ID_SYSTEM);
-		sqlite3_bind_int  (stmt,  3, MR_CONTACT_ID_SYSTEM);
-		sqlite3_bind_int64(stmt,  4, mr_create_smeared_timestamp__());
-		sqlite3_bind_int  (stmt,  5, MR_MSG_TEXT);
-		sqlite3_bind_int  (stmt,  6, MR_STATE_IN_NOTICED);
-		sqlite3_bind_text (stmt,  7, text,  -1, SQLITE_STATIC);
-		if( sqlite3_step(stmt) != SQLITE_DONE ) {
-			mrmailbox_log_error(mailbox, 0, "Cannot add system message to database.");
-			goto cleanup;
-		}
-
-		msg_id = sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj);
+		mrmailbox_add_device_msg__(mailbox, chat_id, text, mr_create_smeared_timestamp__());
 
 	mrsqlite3_unlock(mailbox->m_sql);
 	locked = 0;
 
+	mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, msg_id);
+
 cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 	return msg_id;
@@ -3058,7 +3071,7 @@ int mrmailbox_set_chat_name(mrmailbox_t* mailbox, uint32_t chat_id, const char*
 	{
 		msg->m_type = MR_MSG_TEXT;
 		msg->m_text = mrstock_str_repl_string2(MR_STR_MSGGRPNAME, chat->m_name, new_name);
-		mrparam_set_int(msg->m_param, MRP_SYSTEM_CMD, MR_SYSTEM_GROUPNAME_CHANGED);
+		mrparam_set_int(msg->m_param, MRP_CMD, MR_CMD_GROUPNAME_CHANGED);
 		msg->m_id = mrmailbox_send_msg_object(mailbox, chat_id, msg);
 		mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, msg->m_id);
 	}
@@ -3130,8 +3143,8 @@ int mrmailbox_set_chat_profile_image(mrmailbox_t* mailbox, uint32_t chat_id, con
 	/* send a status mail to all group members, also needed for outself to allow multi-client */
 	if( DO_SEND_STATUS_MAILS )
 	{
-		mrparam_set_int(msg->m_param, MRP_SYSTEM_CMD,       MR_SYSTEM_GROUPIMAGE_CHANGED);
-		mrparam_set    (msg->m_param, MRP_SYSTEM_CMD_PARAM, new_image);
+		mrparam_set_int(msg->m_param, MRP_CMD,       MR_CMD_GROUPIMAGE_CHANGED);
+		mrparam_set    (msg->m_param, MRP_CMD_PARAM, new_image);
 		msg->m_type = MR_MSG_TEXT;
 		msg->m_text = mrstock_str(new_image? MR_STR_MSGGRPIMGCHANGED : MR_STR_MSGGRPIMGDELETED);
 		msg->m_id = mrmailbox_send_msg_object(mailbox, chat_id, msg);
@@ -3271,8 +3284,8 @@ int mrmailbox_add_contact_to_chat(mrmailbox_t* mailbox, uint32_t chat_id, uint32
 	{
 		msg->m_type = MR_MSG_TEXT;
 		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_SYSTEM_CMD, MR_SYSTEM_MEMBER_ADDED_TO_GROUP);
-		mrparam_set    (msg->m_param, MRP_SYSTEM_CMD_PARAM, 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);
 		msg->m_id = mrmailbox_send_msg_object(mailbox, chat_id, msg);
 		mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, msg->m_id);
 	}
@@ -3350,8 +3363,8 @@ int mrmailbox_remove_contact_from_chat(mrmailbox_t* mailbox, uint32_t chat_id, u
 			else {
 				msg->m_text = mrstock_str_repl_string(MR_STR_MSGDELMEMBER, (contact->m_authname&&contact->m_authname[0])? contact->m_authname : contact->m_addr);
 			}
-			mrparam_set_int(msg->m_param, MRP_SYSTEM_CMD, MR_SYSTEM_MEMBER_REMOVED_FROM_GROUP);
-			mrparam_set    (msg->m_param, MRP_SYSTEM_CMD_PARAM, contact->m_addr);
+			mrparam_set_int(msg->m_param, MRP_CMD,       MR_CMD_MEMBER_REMOVED_FROM_GROUP);
+			mrparam_set    (msg->m_param, MRP_CMD_PARAM, contact->m_addr);
 			msg->m_id = mrmailbox_send_msg_object(mailbox, chat_id, msg);
 			mailbox->m_cb(mailbox, MR_EVENT_MSGS_CHANGED, chat_id, msg->m_id);
 		}
@@ -3562,7 +3575,7 @@ void mrmailbox_scaleup_contact_origin__(mrmailbox_t* mailbox, uint32_t contact_i
 int mrmailbox_is_contact_blocked__(mrmailbox_t* mailbox, uint32_t contact_id)
 {
 	int          is_blocked = 0;
-	mrcontact_t* contact = mrcontact_new();
+	mrcontact_t* contact = mrcontact_new(mailbox);
 
 	if( mrcontact_load_from_db__(contact, mailbox->m_sql, contact_id) ) { /* we could optimize this by loading only the needed fields */
 		if( contact->m_blocked ) {
@@ -3579,7 +3592,7 @@ int mrmailbox_get_contact_origin__(mrmailbox_t* mailbox, uint32_t contact_id, in
 {
 	int          ret = 0;
 	int          dummy; if( ret_blocked==NULL ) { ret_blocked = &dummy; }
-	mrcontact_t* contact = mrcontact_new();
+	mrcontact_t* contact = mrcontact_new(mailbox);
 
 	*ret_blocked = 0;
 
@@ -3886,7 +3899,7 @@ cleanup:
  */
 mrcontact_t* mrmailbox_get_contact(mrmailbox_t* mailbox, uint32_t contact_id)
 {
-	mrcontact_t* ret = mrcontact_new();
+	mrcontact_t* ret = mrcontact_new(mailbox);
 
 	mrsqlite3_lock(mailbox->m_sql);
 
@@ -3976,7 +3989,7 @@ void mrmailbox_unblock_chat__(mrmailbox_t* mailbox, uint32_t chat_id)
 void mrmailbox_block_contact(mrmailbox_t* mailbox, uint32_t contact_id, int new_blocking)
 {
 	int locked = 0, send_event = 0, transaction_pending = 0;
-	mrcontact_t*  contact = mrcontact_new();
+	mrcontact_t*  contact = mrcontact_new(mailbox);
 	sqlite3_stmt* stmt;
 
 	if( mailbox == NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || contact_id <= MR_CONTACT_ID_LAST_SPECIAL ) {
@@ -4064,8 +4077,8 @@ char* mrmailbox_get_contact_encrinfo(mrmailbox_t* mailbox, uint32_t contact_id)
 	int             e2ee_enabled = 0;
 	int             explain_id = 0;
 	mrloginparam_t* loginparam = mrloginparam_new();
-	mrcontact_t*    contact = mrcontact_new();
-	mrapeerstate_t* peerstate = mrapeerstate_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;
@@ -4245,7 +4258,7 @@ int mrmailbox_contact_addr_equals__(mrmailbox_t* mailbox, uint32_t contact_id, c
 {
 	int addr_are_equal = 0;
 	if( other_addr ) {
-		mrcontact_t* contact = mrcontact_new();
+		mrcontact_t* contact = mrcontact_new(mailbox);
 		if( mrcontact_load_from_db__(contact, mailbox->m_sql, contact_id) ) {
 			if( contact->m_addr ) {
 				if( strcasecmp(contact->m_addr, other_addr)==0 ) {
@@ -4343,20 +4356,20 @@ int mrmailbox_rfc724_mid_cnt__(mrmailbox_t* mailbox, const char* rfc724_mid)
 
 /* check, if the given Message-ID exists in the database (if not, the message is normally downloaded from the server and parsed,
 so, we should even keep unuseful messages in the database (we can leave the other fields empty to save space) */
-int mrmailbox_rfc724_mid_exists__(mrmailbox_t* mailbox, const char* rfc724_mid, char** ret_server_folder, uint32_t* ret_server_uid)
+uint32_t mrmailbox_rfc724_mid_exists__(mrmailbox_t* mailbox, const char* rfc724_mid, char** ret_server_folder, uint32_t* ret_server_uid)
 {
 	sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_ss_FROM_msgs_WHERE_m,
-		"SELECT server_folder, server_uid FROM msgs WHERE rfc724_mid=?;");
+		"SELECT server_folder, server_uid, id FROM msgs WHERE rfc724_mid=?;");
 	sqlite3_bind_text(stmt, 1, rfc724_mid, -1, SQLITE_STATIC);
 	if( sqlite3_step(stmt) != SQLITE_ROW ) {
-		*ret_server_folder = NULL;
-		*ret_server_uid = 0;
+		if( ret_server_folder ) { *ret_server_folder = NULL; }
+		if( ret_server_uid )    { *ret_server_uid    = 0; }
 		return 0;
 	}
 
-	*ret_server_folder = safe_strdup((char*)sqlite3_column_text(stmt, 0));
-	*ret_server_uid = sqlite3_column_int(stmt, 1); /* may be 0 */
-	return 1;
+	if( ret_server_folder ) { *ret_server_folder = safe_strdup((char*)sqlite3_column_text(stmt, 0)); }
+	if( ret_server_uid )    { *ret_server_uid = sqlite3_column_int(stmt, 1); /* may be 0 */ }
+	return sqlite3_column_int(stmt, 2);
 }
 
 
@@ -4442,6 +4455,7 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	int            locked = 0;
 	sqlite3_stmt*  stmt;
 	mrmsg_t*       msg = mrmsg_new();
+	mrcontact_t*   contact_from = mrcontact_new(mailbox);
 	char           *rawtxt = NULL, *p;
 
 	mrstrbuilder_init(&ret, 0);
@@ -4454,6 +4468,7 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	locked = 1;
 
 		mrmsg_load_from_db__(msg, mailbox, msg_id);
+		mrcontact_load_from_db__(contact_from, mailbox->m_sql, msg->m_from_id);
 
 		stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_txt_raw_FROM_msgs_WHERE_id,
 			"SELECT txt_raw FROM msgs WHERE id=?;");
@@ -4493,44 +4508,53 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 		mrstrbuilder_cat(&ret, "\n");
 	}
 
-	if( msg->m_from_id == MR_CONTACT_ID_SYSTEM || msg->m_to_id == MR_CONTACT_ID_SYSTEM ) { // do not use mrmsg_is_systemcmd() as this would als catch system messages sent as real messages by others
-		goto cleanup; // 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 state */
+	p = NULL;
+	switch( msg->m_state ) {
+		case MR_STATE_IN_FRESH:      p = safe_strdup("Fresh");           break;
+		case MR_STATE_IN_NOTICED:    p = safe_strdup("Noticed");         break;
+		case MR_STATE_IN_SEEN:       p = safe_strdup("Seen");            break;
+		case MR_STATE_OUT_DELIVERED: p = safe_strdup("Delivered");       break;
+		case MR_STATE_OUT_ERROR:     p = safe_strdup("Error");           break;
+		case MR_STATE_OUT_MDN_RCVD:  p = safe_strdup("Read");            break;
+		case MR_STATE_OUT_PENDING:   p = safe_strdup("Pending");         break;
+		default:                     p = mr_mprintf("%i", msg->m_state); break;
 	}
+	mrstrbuilder_catf(&ret, "State: %s", p);
+	free(p);
 
-	/* add encryption state */
+	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("End-to-end, bad signature");
+			p = safe_strdup("Encrypted, bad signature");
 		}
 		else if( e2ee_errors&MR_VALIDATE_UNKNOWN_SIGNATURE ) {
-			p = safe_strdup("End-to-end, unknown signature");
+			p = safe_strdup("Encrypted, unknown signature");
 		}
 		else {
-			p = safe_strdup("End-to-end, no signature");
+			p = safe_strdup("Encrypted, no signature");
 		}
 	}
 	else if( mrparam_get_int(msg->m_param, MRP_GUARANTEE_E2EE, 0) ) {
-		if( !msg->m_mailbox->m_e2ee_enabled ) {
-			p = safe_strdup("End-to-end, transport for replies");
-		}
-		else {
-			p = safe_strdup("End-to-end");
-		}
+		p = safe_strdup("Encrypted");
 	}
-	else {
-		p = safe_strdup("Transport");
+
+	if( p ) {
+		mrstrbuilder_catf(&ret, ", %s", p);
+		free(p);
 	}
-	mrstrbuilder_cat(&ret, "Encryption: ");
-	mrstrbuilder_cat(&ret, p); free(p);
 	mrstrbuilder_cat(&ret, "\n");
 
-	/* add "suspicious" status */
-	if( msg->m_state==MR_STATE_IN_FRESH ) {
-		mrstrbuilder_cat(&ret, "Status: Fresh\n");
-	}
-	else if( msg->m_state==MR_STATE_IN_NOTICED ) {
-		mrstrbuilder_cat(&ret, "Status: Noticed\n");
+	/* add sender (only for info messages as the avatar may not be shown for them) */
+	if( mrmsg_is_info(msg) ) {
+		mrstrbuilder_cat(&ret, "Sender: ");
+		p = mrcontact_get_name_n_addr(contact_from); mrstrbuilder_cat(&ret, p); free(p);
+		mrstrbuilder_cat(&ret, "\n");
 	}
 
 	/* add file info */
@@ -4540,7 +4564,18 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	}
 
 	if( msg->m_type != MR_MSG_TEXT ) {
-		p = mr_mprintf("Type: %i\n", msg->m_type); mrstrbuilder_cat(&ret, p); free(p);
+		p = NULL;
+		switch( msg->m_type )  {
+			case MR_MSG_AUDIO: p = safe_strdup("Audio");          break;
+			case MR_MSG_FILE:  p = safe_strdup("File");           break;
+			case MR_MSG_GIF:   p = safe_strdup("GIF");            break;
+			case MR_MSG_IMAGE: p = safe_strdup("Image");          break;
+			case MR_MSG_VIDEO: p = safe_strdup("Video");          break;
+			case MR_MSG_VOICE: p = safe_strdup("Voice");          break;
+			default:           p = mr_mprintf("%i", msg->m_type); break;
+		}
+		mrstrbuilder_catf(&ret, "Type: %s\n", p);
+		free(p);
 	}
 
 	int w = mrparam_get_int(msg->m_param, MRP_WIDTH, 0), h = mrparam_get_int(msg->m_param, MRP_HEIGHT, 0);
@@ -4564,7 +4599,15 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 	#ifdef __ANDROID__
 		mrstrbuilder_cat(&ret, "<c#808080>");
 	#endif
-	mrstrbuilder_catf(&ret, "\nMessage-ID: %s\nLast seen as: %s/%i", msg->m_rfc724_mid, msg->m_server_folder, (int)msg->m_server_uid);
+
+	if( msg->m_rfc724_mid && msg->m_rfc724_mid[0] ) {
+		mrstrbuilder_catf(&ret, "\nMessage-ID: %s", msg->m_rfc724_mid);
+	}
+
+	if( msg->m_server_folder && msg->m_server_folder[0] ) {
+		mrstrbuilder_catf(&ret, "\nLast seen as: %s/%i", msg->m_server_folder, (int)msg->m_server_uid);
+	}
+
 	#ifdef __ANDROID__
 		mrstrbuilder_cat(&ret, "</c>");
 	#endif
@@ -4572,6 +4615,7 @@ char* mrmailbox_get_msg_info(mrmailbox_t* mailbox, uint32_t msg_id)
 cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 	mrmsg_unref(msg);
+	mrcontact_unref(contact_from);
 	free(rawtxt);
 	return ret.m_buf;
 }
@@ -4596,7 +4640,7 @@ void mrmailbox_forward_msgs(mrmailbox_t* mailbox, const uint32_t* msg_ids, int m
 {
 	mrmsg_t*      msg = mrmsg_new();
 	mrchat_t*     chat = mrchat_new(mailbox);
-	mrcontact_t*  contact = mrcontact_new();
+	mrcontact_t*  contact = mrcontact_new(mailbox);
 	int           locked = 0, transaction_pending = 0;
 	carray*       created_db_entries = carray_new(16);
 	char*         idsstr = NULL, *q3 = NULL;
@@ -4633,6 +4677,8 @@ 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);
 
 			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);
@@ -4717,7 +4763,8 @@ void mrmailbox_delete_msg_on_imap(mrmailbox_t* mailbox, mrjob_t* job)
 	mrsqlite3_lock(mailbox->m_sql);
 	locked = 1;
 
-		if( !mrmsg_load_from_db__(msg, mailbox, job->m_foreign_id) ) {
+		if( !mrmsg_load_from_db__(msg, mailbox, job->m_foreign_id)
+		 || msg->m_rfc724_mid == NULL || msg->m_rfc724_mid[0] == 0 /* eg. device messages have no Message-ID */ ) {
 			goto cleanup;
 		}
 
@@ -4832,7 +4879,7 @@ void mrmailbox_delete_msgs(mrmailbox_t* mailbox, const uint32_t* msg_ids, int ms
 		for( i = 0; i < msg_cnt; i++ )
 		{
 			mrmailbox_update_msg_chat_id__(mailbox, msg_ids[i], MR_CHAT_ID_TRASH);
-			mrjob_add__(mailbox, MRJ_DELETE_MSG_ON_IMAP, msg_ids[i], NULL); /* results in a call to mrmailbox_delete_msg_on_imap() */
+			mrjob_add__(mailbox, MRJ_DELETE_MSG_ON_IMAP, msg_ids[i], NULL, 0); /* results in a call to mrmailbox_delete_msg_on_imap() */
 		}
 
 	mrsqlite3_commit__(mailbox->m_sql);
@@ -4896,7 +4943,7 @@ void mrmailbox_markseen_msg_on_imap(mrmailbox_t* mailbox, mrjob_t* job)
 
 				if( out_ms_flags&MR_MS_MDNSent_JUST_SET )
 				{
-					mrjob_add__(mailbox, MRJ_SEND_MDN, msg->m_id, NULL); /* results in a call to mrmailbox_send_mdn() */
+					mrjob_add__(mailbox, MRJ_SEND_MDN, msg->m_id, NULL, 0); /* results in a call to mrmailbox_send_mdn() */
 				}
 
 			mrsqlite3_unlock(mailbox->m_sql);
@@ -4990,7 +5037,7 @@ void mrmailbox_markseen_msgs(mrmailbox_t* mailbox, const uint32_t* msg_ids, int
 				if( curr_state == MR_STATE_IN_FRESH || curr_state == MR_STATE_IN_NOTICED ) {
 					mrmailbox_update_msg_state__(mailbox, msg_ids[i], MR_STATE_IN_SEEN);
 					mrmailbox_log_info(mailbox, 0, "Seen message #%i.", msg_ids[i]);
-					mrjob_add__(mailbox, MRJ_MARKSEEN_MSG_ON_IMAP, msg_ids[i], NULL); /* results in a call to mrmailbox_markseen_msg_on_imap() */
+					mrjob_add__(mailbox, MRJ_MARKSEEN_MSG_ON_IMAP, msg_ids[i], NULL, 0); /* results in a call to mrmailbox_markseen_msg_on_imap() */
 					send_event = 1;
 				}
 			}
@@ -5125,7 +5172,7 @@ void mrmailbox_send_mdn(mrmailbox_t* mailbox, mrjob_t* job)
 	}
 
     if( !mrmimefactory_load_mdn(&mimefactory, job->m_foreign_id)
-     || !mrmimefactory_render(&mimefactory, 0/*encrypt to self*/) ) {
+     || !mrmimefactory_render(&mimefactory) ) {
 		goto cleanup;
     }
 

+ 3 - 3
deltachat-ios/libraries/deltachat-core/src/mrmailbox.h

@@ -303,7 +303,7 @@ void            mrmailbox_heartbeat         (mrmailbox_t*);
 
 
 /* out-of-band verification */
-#define         MR_QR_FPR_ASK_OOB           200 /* id=contact */
+#define         MR_QR_ASK_SECUREJOIN        200 /* id=contact */
 #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 */
@@ -311,8 +311,8 @@ void            mrmailbox_heartbeat         (mrmailbox_t*);
 #define         MR_QR_TEXT                  330 /* text1=text */
 #define         MR_QR_ERROR                 400 /* text1=error string */
 mrlot_t*        mrmailbox_check_qr          (mrmailbox_t*, const char* qr);
-char*           mrmailbox_oob_get_qr        (mrmailbox_t*);
-int             mrmailbox_oob_join          (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);
 
 
 /* deprecated functions */

+ 18 - 5
deltachat-ios/libraries/deltachat-core/src/mrmailbox_e2ee.c

@@ -318,8 +318,9 @@ 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 encrypt_to_self, struct mailmime* in_out_message, mrmailbox_e2ee_helper_t* helper)
+                    struct mailmime* in_out_message, mrmailbox_e2ee_helper_t* helper)
 {
 	int                    locked = 0, col = 0, do_encrypt = 0;
 	mraheader_t*           autocryptheader = mraheader_new();
@@ -364,7 +365,7 @@ void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
 			clistiter*      iter1;
 			for( iter1 = clist_begin(recipients_addr); iter1!=NULL ; iter1=clist_next(iter1) ) {
 				const char* recipient_addr = clist_content(iter1);
-				mrapeerstate_t* peerstate = mrapeerstate_new();
+				mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
 				if( mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, recipient_addr)
 				 && mrapeerstate_peek_key(peerstate)
 				 && (peerstate->m_prefer_encrypt==MRA_PE_MUTUAL || e2ee_guaranteed) )
@@ -387,6 +388,10 @@ void mrmailbox_e2ee_encrypt(mrmailbox_t* mailbox, const clist* recipients_addr,
 			}
 		}
 
+		if( force_unencrypted ) {
+			do_encrypt = 0;
+		}
+
 	mrsqlite3_unlock(mailbox->m_sql);
 	locked = 0;
 
@@ -733,7 +738,7 @@ static void update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time,
 					if( mrhash_find(recipients, gossip_header->m_addr, strlen(gossip_header->m_addr)) )
 					{
 						/* valid recipient: update peerstate */
-						mrapeerstate_t* peerstate = mrapeerstate_new();
+						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*/);
@@ -761,20 +766,24 @@ static void update_gossip_peerstates(mrmailbox_t* mailbox, time_t message_time,
 }
 
 
-int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message, int* ret_validation_errors)
+int mrmailbox_e2ee_decrypt(mrmailbox_t* mailbox, struct mailmime* in_out_message, int* ret_validation_errors, int* ret_degrade_event)
 {
 	/* 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 */
 	struct mailimf_fields* imffields = mailmime_find_mailimf_fields(in_out_message); /*just a pointer into mailmime structure, must not be freed*/
 	mraheader_t*           autocryptheader = NULL;
 	time_t                 message_time = 0;
-	mrapeerstate_t*        peerstate = mrapeerstate_new();
+	mrapeerstate_t*        peerstate = mrapeerstate_new(mailbox);
 	int                    locked = 0;
 	char*                  from = NULL, *self_addr = NULL;
 	mrkeyring_t*           private_keyring = mrkeyring_new();
 	int                    sth_decrypted = 0;
 	struct mailimf_fields* gossip_headers = NULL;
 
+	if( ret_degrade_event ) {
+		*ret_degrade_event = 0;
+	}
+
 	if( mailbox==NULL || mailbox->m_magic != MR_MAILBOX_MAGIC || in_out_message==NULL || ret_validation_errors==NULL
 	 || imffields==NULL || peerstate==NULL || private_keyring==NULL ) {
 		goto cleanup;
@@ -838,6 +847,10 @@ 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;

+ 4 - 3
deltachat-ios/libraries/deltachat-core/src/mrmailbox_imex.c

@@ -508,9 +508,10 @@ char* mrmailbox_initiate_key_transfer(mrmailbox_t* mailbox)
 
 	msg = mrmsg_new();
 	msg->m_type = MR_MSG_FILE;
-	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_SYSTEM_CMD, MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE);
+	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
 
 	CHECK_EXIT
 

+ 29 - 0
deltachat-ios/libraries/deltachat-core/src/mrmailbox_keyhistory.c

@@ -0,0 +1,29 @@
+/*******************************************************************************
+ *
+ *                              Delta Chat Core
+ *                      Copyright (C) 2018 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"
+
+
+void mrmailbox_add_to_keyhistory__(mrmailbox_t* mailbox, const char* rfc724_mid, time_t sending_time, const char* addr, const char* fingerprint)
+{
+	// TODO
+}

+ 2 - 8
deltachat-ios/libraries/deltachat-core/src/mrmailbox_log.c

@@ -95,22 +95,16 @@ static void mrmailbox_log_vprintf(mrmailbox_t* mailbox, int event, int code, con
 	else if( msg_format )
 	{
 		#define BUFSIZE 1024
-		char tempbuf[BUFSIZE];
+		char tempbuf[BUFSIZE+1];
 		vsnprintf(tempbuf, BUFSIZE, msg_format, va);
 		msg = safe_strdup(tempbuf);
-
-		if( event == MR_EVENT_ERROR ) {
-			char* temp = msg;
-			msg = mrstock_str_repl_string(MR_STR_ERROR, temp);
-			free(temp);
-		}
 	}
 
 	/* if we have still no message, create one based upon  the code */
 	if( msg == NULL ) {
 		     if( event == MR_EVENT_INFO )    { msg = mr_mprintf("Info: %i",    (int)code); }
 		else if( event == MR_EVENT_WARNING ) { msg = mr_mprintf("Warning: %i", (int)code); }
-		else                                 { msg = mrstock_str_repl_int(MR_STR_ERROR, code); }
+		else                                 { msg = mr_mprintf("Error: %i",   (int)code); }
 	}
 
 	/* prefix the message by the thread-id? we do this for non-errros that are normally only logged (for the few errros, the thread should be clear (enough)) */

+ 260 - 0
deltachat-ios/libraries/deltachat-core/src/mrmailbox_qr.c

@@ -0,0 +1,260 @@
+/*******************************************************************************
+ *
+ *                              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 <stdarg.h>
+#include <unistd.h>
+#include "mrmailbox_internal.h"
+#include "mrapeerstate.h"
+
+
+#define MAILTO_SCHEME      "mailto:"
+#define MATMSG_SCHEME      "MATMSG:"
+#define VCARD_BEGIN        "BEGIN:VCARD"
+#define SMTP_SCHEME        "SMTP:"
+
+
+/**
+ * Check a scanned QR code.
+ * The function should be called after a QR code is scanned.
+ * The function takes the raw text scanned and checks what can be done with it.
+ *
+ * @memberof mrmailbox_t
+ *
+ * @param mailbox The mailbox object.
+ * @param qr The text of the scanned QR code.
+ *
+ * @return Parsed QR code as an mrlot_t object.
+ */
+mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
+{
+	int             locked        = 0;
+	char*           payload       = NULL;
+	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;
+	mrapeerstate_t* peerstate     = mrapeerstate_new(mailbox);
+	mrlot_t*        qr_parsed     = mrlot_new();
+	uint32_t        chat_id       = 0;
+	char*           device_msg    = NULL;
+
+	qr_parsed->m_state = 0;
+
+	if( mailbox==NULL || mailbox->m_magic!=MR_MAILBOX_MAGIC || qr==NULL ) {
+		goto cleanup;
+	}
+
+	mrmailbox_log_info(mailbox, 0, "Scanned QR code: %s", qr);
+
+	/* split parameters from the qr code
+	 ------------------------------------ */
+
+	if( strncasecmp(qr, OPENPGP4FPR_SCHEME, strlen(OPENPGP4FPR_SCHEME)) == 0 )
+	{
+		/* scheme: OPENPGP4FPR:1234567890123456789012345678901234567890#v=mail%40domain.de&n=Name */
+		payload  = safe_strdup(&qr[strlen(OPENPGP4FPR_SCHEME)]);
+		char* fragment = strchr(payload, '#'); /* must not be freed, only a pointer inside payload */
+		if( fragment )
+		{
+			*fragment = 0;
+			fragment++;
+
+			mrparam_t* param = mrparam_new();
+			mrparam_set_urlencoded(param, fragment);
+
+			addr = mrparam_get(param, 'v', NULL);
+			if( addr ) {
+				char* name_urlencoded = mrparam_get(param, 'n', NULL);
+				if( name_urlencoded ) {
+					name = mr_url_decode(name_urlencoded);
+					mr_normalize_name(name);
+					free(name_urlencoded);
+				}
+				random_public = mrparam_get(param, 'p', "");
+				random_secret = mrparam_get(param, 's', "");
+			}
+
+			mrparam_unref(param);
+		}
+
+		fingerprint = mr_normalize_fingerprint(payload);
+	}
+	else if( strncasecmp(qr, MAILTO_SCHEME, strlen(MAILTO_SCHEME)) == 0 )
+	{
+		/* scheme: mailto:addr...?subject=...&body=... */
+		payload = safe_strdup(&qr[strlen(MAILTO_SCHEME)]);
+		char* query = strchr(payload, '?'); /* must not be freed, only a pointer inside payload */
+		if( query ) {
+			*query = 0;
+		}
+		addr = safe_strdup(payload);
+	}
+	else if( strncasecmp(qr, SMTP_SCHEME, strlen(SMTP_SCHEME)) == 0 )
+	{
+		/* scheme: `SMTP:addr...:subject...:body...` */
+		payload = safe_strdup(&qr[strlen(SMTP_SCHEME)]);
+		char* colon = strchr(payload, ':'); /* must not be freed, only a pointer inside payload */
+		if( colon ) {
+			*colon = 0;
+		}
+		addr = safe_strdup(payload);
+	}
+	else if( strncasecmp(qr, MATMSG_SCHEME, strlen(MATMSG_SCHEME)) == 0 )
+	{
+		/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
+		char* to = strstr(qr, "TO:"); /* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
+		if( to ) {
+			addr = safe_strdup(&to[3]);
+			char* semicolon = strchr(addr, ';');
+			if( semicolon ) { *semicolon = 0; }
+		}
+		else {
+			qr_parsed->m_state = MR_QR_ERROR;
+			qr_parsed->m_text1 = safe_strdup("Bad e-mail address.");
+			goto cleanup;
+		}
+	}
+	else if( strncasecmp(qr, VCARD_BEGIN, strlen(VCARD_BEGIN)) == 0 )
+	{
+		/* scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL:addr...;` */
+		carray* lines = mr_split_into_lines(qr);
+		for( int i = 0; i < carray_count(lines); i++ ) {
+			char* key   = (char*)carray_get(lines, i); mr_trim(key);
+			char* value = strchr(key, ':');
+			if( value ) {
+				*value = 0;
+				value++;
+				char* semicolon = strchr(key, ';'); if( semicolon ) { *semicolon = 0; } /* handle `EMAIL;type=work:` stuff */
+				if( strcasecmp(key, "EMAIL") == 0 ) {
+					semicolon = strchr(value, ';'); if( semicolon ) { *semicolon = 0; } /* use the first EMAIL */
+					addr = safe_strdup(value);
+				}
+				else if( strcasecmp(key, "N") == 0 ) {
+					semicolon = strchr(value, ';'); if( semicolon ) { semicolon = strchr(semicolon+1, ';'); if( semicolon ) { *semicolon = 0; } } /* the N format is `lastname;prename;wtf;title` - skip everything after the second semicolon */
+					name = safe_strdup(value);
+					mr_str_replace(&name, ";", ","); /* the format "lastname,prename" is handled by mr_normalize_name() */
+					mr_normalize_name(name);
+				}
+			}
+		}
+		mr_free_splitted_lines(lines);
+	}
+
+	/* check the paramters
+	  ---------------------- */
+
+	if( addr ) {
+		char* temp = mr_url_decode(addr);     free(addr); addr = temp; /* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
+		      temp = mr_normalize_addr(addr); free(addr); addr = temp;
+
+		if( strlen(addr) < 3 || strchr(addr, '@')==NULL || strchr(addr, '.')==NULL ) {
+			qr_parsed->m_state = MR_QR_ERROR;
+			qr_parsed->m_text1 = safe_strdup("Bad e-mail address.");
+			goto cleanup;
+		}
+	}
+
+	if( fingerprint ) {
+		if( strlen(fingerprint) != 40 ) {
+			qr_parsed->m_state = MR_QR_ERROR;
+			qr_parsed->m_text1 = safe_strdup("Bad fingerprint length in QR code.");
+			goto cleanup;
+		}
+	}
+
+	/* let's see what we can do with the parameters
+	  ---------------------------------------------- */
+
+	if( fingerprint )
+	{
+		/* fingerprint set ... */
+
+		if( addr == NULL || random_public == NULL || random_secret == NULL )
+		{
+			// _only_ fingerprint set ...
+			// (we could also do this before/instead of a secure-join, however, this may require complicated questions in the ui)
+			mrsqlite3_lock(mailbox->m_sql);
+			locked = 1;
+
+				if( mrapeerstate_load_by_fingerprint__(peerstate, mailbox->m_sql, fingerprint) ) {
+					qr_parsed->m_state = MR_QR_FPR_OK;
+					qr_parsed->m_id    = mrmailbox_add_or_lookup_contact__(mailbox, NULL, peerstate->m_addr, MR_ORIGIN_UNHANDLED_QR_SCAN, NULL);
+
+					mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, qr_parsed->m_id, MR_CHAT_DEADDROP_BLOCKED, &chat_id, NULL);
+					device_msg = mr_mprintf("%s verified.", peerstate->m_addr);
+				}
+				else {
+					qr_parsed->m_text1 = mr_format_fingerprint(fingerprint);
+					qr_parsed->m_state = MR_QR_FPR_WITHOUT_ADDR;
+				}
+
+			mrsqlite3_unlock(mailbox->m_sql);
+			locked = 0;
+		}
+		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)
+			mrsqlite3_lock(mailbox->m_sql);
+			locked = 1;
+
+				qr_parsed->m_state         = MR_QR_ASK_SECUREJOIN;
+				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);
+
+			mrsqlite3_unlock(mailbox->m_sql);
+			locked = 0;
+		}
+	}
+	else if( addr )
+	{
+        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
+	{
+        qr_parsed->m_state = MR_QR_TEXT;
+		qr_parsed->m_text1 = safe_strdup(qr);
+	}
+
+	if( device_msg ) {
+		mrmailbox_add_device_msg(mailbox, chat_id, device_msg);
+	}
+
+cleanup:
+	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
+	free(addr);
+	free(fingerprint);
+	mrapeerstate_unref(peerstate);
+	free(payload);
+	free(name);
+	free(random_public);
+	free(random_secret);
+	free(device_msg);
+	return qr_parsed;
+}
+
+

+ 32 - 8
deltachat-ios/libraries/deltachat-core/src/mrmailbox_receive_imf.c

@@ -276,6 +276,20 @@ 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,
                                         time_t* sort_timestamp, time_t* sent_timestamp, time_t* rcvd_timestamp)
 {
@@ -661,19 +675,19 @@ static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_
 
 		if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-Member-Removed", "X-MrRemoveFromGrp"))!=NULL ) {
 			X_MrRemoveFromGrp = optional_field->fld_value;
-			mime_parser->m_is_system_message = MR_SYSTEM_MEMBER_REMOVED_FROM_GROUP;
+			mime_parser->m_is_system_message = MR_CMD_MEMBER_REMOVED_FROM_GROUP;
 		}
 		else if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-Member-Added", "X-MrAddToGrp"))!=NULL ) {
 			X_MrAddToGrp = optional_field->fld_value;
-			mime_parser->m_is_system_message = MR_SYSTEM_MEMBER_ADDED_TO_GROUP;
+			mime_parser->m_is_system_message = MR_CMD_MEMBER_ADDED_TO_GROUP;
 		}
 		else if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-Name-Changed", "X-MrGrpNameChanged"))!=NULL ) {
 			X_MrGrpNameChanged = 1;
-			mime_parser->m_is_system_message = MR_SYSTEM_GROUPNAME_CHANGED;
+			mime_parser->m_is_system_message = MR_CMD_GROUPNAME_CHANGED;
 		}
 		else if( (optional_field=mrmimeparser_lookup_optional_field(mime_parser, "Chat-Group-Image"))!=NULL ) {
 			X_MrGrpImageChanged = 1;
-			mime_parser->m_is_system_message = MR_SYSTEM_GROUPIMAGE_CHANGED;
+			mime_parser->m_is_system_message = MR_CMD_GROUPIMAGE_CHANGED;
 		}
 	}
 
@@ -871,6 +885,8 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 	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);
@@ -1111,7 +1127,7 @@ 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_oob_is_handshake_message__(mailbox, mime_parser);
+			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 ) {
@@ -1186,6 +1202,10 @@ 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)) */
@@ -1202,7 +1222,7 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 				}
 
 				if( mime_parser->m_is_system_message ) {
-					mrparam_set_int(part->m_param, MRP_SYSTEM_CMD, mime_parser->m_is_system_message);
+					mrparam_set_int(part->m_param, MRP_CMD, mime_parser->m_is_system_message);
 				}
 
 				stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_msscftttsmttpb,
@@ -1347,7 +1367,7 @@ void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_termina
 					a classical deadlock, see also (***) in mrimap.c */
 					if( mime_parser->m_is_send_by_messenger || mdn_consumed ) {
 						char* jobparam = mr_mprintf("%c=%s\n%c=%lu", MRP_SERVER_FOLDER, server_folder, MRP_SERVER_UID, server_uid);
-							mrjob_add__(mailbox, MRJ_MARKSEEN_MDN_ON_IMAP, 0, jobparam);
+							mrjob_add__(mailbox, MRJ_MARKSEEN_MDN_ON_IMAP, 0, jobparam, 0);
 						free(jobparam);
 					}
 				}
@@ -1376,7 +1396,11 @@ cleanup:
 	if( db_locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 
 	if( is_handshake_message ) {
-		mrmailbox_oob_handle_handshake_message(mailbox, mime_parser, chat_id); /* must be called after unlocking before deletion of mime_parser */
+		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);

+ 138 - 276
deltachat-ios/libraries/deltachat-core/src/mrmailbox_oob.c → deltachat-ios/libraries/deltachat-core/src/mrmailbox_securejoin.c

@@ -27,13 +27,19 @@
 #include "mrapeerstate.h"
 #include "mrmimeparser.h"
 #include "mrmimefactory.h"
+#include "mrjob.h"
 
 
 /*******************************************************************************
- * Alice's random_public and random_secret mini-datastore
+ * Tools: Alice's random_public and random_secret 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
+comparison when the data are provided by Bob. */
+
+
 static void store_random__(mrmailbox_t* mailbox, const char* datastore_name, const char* to_add)
 {
 	// prepend new random to the list of all tags
@@ -82,242 +88,11 @@ static int lookup_random__(mrmailbox_t* mailbox, const char* datastore_name, con
 }
 
 
-
 /*******************************************************************************
- * Tools
+ * Tools: Misc.
  ******************************************************************************/
 
 
-#define OPENPGP4FPR_SCHEME "OPENPGP4FPR:" /* yes: uppercase */
-#define MAILTO_SCHEME      "mailto:"
-#define MATMSG_SCHEME      "MATMSG:"
-#define VCARD_BEGIN        "BEGIN:VCARD"
-#define SMTP_SCHEME        "SMTP:"
-
-
-/**
- * Check a scanned QR code.
- * The function should be called after a QR code is scanned.
- * The function takes the raw text scanned and checks what can be done with it.
- *
- * @memberof mrmailbox_t
- *
- * @param mailbox The mailbox object.
- * @param qr The text of the scanned QR code.
- *
- * @return Parsed QR code as an mrlot_t object.
- */
-mrlot_t* mrmailbox_check_qr(mrmailbox_t* mailbox, const char* qr)
-{
-	int             locked        = 0;
-	char*           payload       = NULL;
-	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;
-	mrapeerstate_t* peerstate     = mrapeerstate_new();
-	mrlot_t*        qr_parsed     = mrlot_new();
-
-	qr_parsed->m_state = 0;
-
-	if( mailbox==NULL || mailbox->m_magic!=MR_MAILBOX_MAGIC || qr==NULL ) {
-		goto cleanup;
-	}
-
-	mrmailbox_log_info(mailbox, 0, "Scanned QR code: %s", qr);
-
-	/* split parameters from the qr code
-	 ------------------------------------ */
-
-	if( strncasecmp(qr, OPENPGP4FPR_SCHEME, strlen(OPENPGP4FPR_SCHEME)) == 0 )
-	{
-		/* scheme: OPENPGP4FPR:1234567890123456789012345678901234567890#v=mail%40domain.de&n=Name */
-		payload  = safe_strdup(&qr[strlen(OPENPGP4FPR_SCHEME)]);
-		char* fragment = strchr(payload, '#'); /* must not be freed, only a pointer inside payload */
-		if( fragment )
-		{
-			*fragment = 0;
-			fragment++;
-
-			mrparam_t* param = mrparam_new();
-			mrparam_set_urlencoded(param, fragment);
-
-			addr = mrparam_get(param, 'v', NULL);
-			if( addr ) {
-				char* name_urlencoded = mrparam_get(param, 'n', NULL);
-				if( name_urlencoded ) {
-					name = mr_url_decode(name_urlencoded);
-					mr_normalize_name(name);
-					free(name_urlencoded);
-				}
-				random_public = mrparam_get(param, 'p', "");
-				random_secret = mrparam_get(param, 's', "");
-			}
-
-			mrparam_unref(param);
-		}
-
-		fingerprint = mr_normalize_fingerprint(payload);
-	}
-	else if( strncasecmp(qr, MAILTO_SCHEME, strlen(MAILTO_SCHEME)) == 0 )
-	{
-		/* scheme: mailto:addr...?subject=...&body=... */
-		payload = safe_strdup(&qr[strlen(MAILTO_SCHEME)]);
-		char* query = strchr(payload, '?'); /* must not be freed, only a pointer inside payload */
-		if( query ) {
-			*query = 0;
-		}
-		addr = safe_strdup(payload);
-	}
-	else if( strncasecmp(qr, SMTP_SCHEME, strlen(SMTP_SCHEME)) == 0 )
-	{
-		/* scheme: `SMTP:addr...:subject...:body...` */
-		payload = safe_strdup(&qr[strlen(SMTP_SCHEME)]);
-		char* colon = strchr(payload, ':'); /* must not be freed, only a pointer inside payload */
-		if( colon ) {
-			*colon = 0;
-		}
-		addr = safe_strdup(payload);
-	}
-	else if( strncasecmp(qr, MATMSG_SCHEME, strlen(MATMSG_SCHEME)) == 0 )
-	{
-		/* scheme: `MATMSG:TO:addr...;SUB:subject...;BODY:body...;` - there may or may not be linebreaks after the fields */
-		char* to = strstr(qr, "TO:"); /* does not work when the text `TO:` is used in subject/body _and_ TO: is not the first field. we ignore this case. */
-		if( to ) {
-			addr = safe_strdup(&to[3]);
-			char* semicolon = strchr(addr, ';');
-			if( semicolon ) { *semicolon = 0; }
-		}
-		else {
-			qr_parsed->m_state = MR_QR_ERROR;
-			qr_parsed->m_text1 = safe_strdup("Bad e-mail address.");
-			goto cleanup;
-		}
-	}
-	else if( strncasecmp(qr, VCARD_BEGIN, strlen(VCARD_BEGIN)) == 0 )
-	{
-		/* scheme: `VCARD:BEGIN\nN:last name;first name;...;\nEMAIL:addr...;` */
-		carray* lines = mr_split_into_lines(qr);
-		for( int i = 0; i < carray_count(lines); i++ ) {
-			char* key   = (char*)carray_get(lines, i); mr_trim(key);
-			char* value = strchr(key, ':');
-			if( value ) {
-				*value = 0;
-				value++;
-				char* semicolon = strchr(key, ';'); if( semicolon ) { *semicolon = 0; } /* handle `EMAIL;type=work:` stuff */
-				if( strcasecmp(key, "EMAIL") == 0 ) {
-					semicolon = strchr(value, ';'); if( semicolon ) { *semicolon = 0; } /* use the first EMAIL */
-					addr = safe_strdup(value);
-				}
-				else if( strcasecmp(key, "N") == 0 ) {
-					semicolon = strchr(value, ';'); if( semicolon ) { semicolon = strchr(semicolon+1, ';'); if( semicolon ) { *semicolon = 0; } } /* the N format is `lastname;prename;wtf;title` - skip everything after the second semicolon */
-					name = safe_strdup(value);
-					mr_str_replace(&name, ";", ","); /* the format "lastname,prename" is handled by mr_normalize_name() */
-					mr_normalize_name(name);
-				}
-			}
-		}
-		mr_free_splitted_lines(lines);
-	}
-
-	/* check the paramters
-	  ---------------------- */
-
-	if( addr ) {
-		char* temp = mr_url_decode(addr);     free(addr); addr = temp; /* urldecoding is needed at least for OPENPGP4FPR but should not hurt in the other cases */
-		      temp = mr_normalize_addr(addr); free(addr); addr = temp;
-
-		if( strlen(addr) < 3 || strchr(addr, '@')==NULL || strchr(addr, '.')==NULL ) {
-			qr_parsed->m_state = MR_QR_ERROR;
-			qr_parsed->m_text1 = safe_strdup("Bad e-mail address.");
-			goto cleanup;
-		}
-	}
-
-	if( fingerprint ) {
-		if( strlen(fingerprint) != 40 ) {
-			qr_parsed->m_state = MR_QR_ERROR;
-			qr_parsed->m_text1 = safe_strdup("Bad fingerprint length in QR code.");
-			goto cleanup;
-		}
-	}
-
-	/* let's see what we can do with the parameters
-	  ---------------------------------------------- */
-
-	if( fingerprint )
-	{
-		/* fingerprint set ... */
-
-		if( addr == NULL )
-		{
-			/* _only_ fingerprint set ... */
-			mrsqlite3_lock(mailbox->m_sql);
-			locked = 1;
-
-				if( mrapeerstate_load_by_fingerprint__(peerstate, mailbox->m_sql, fingerprint) ) {
-					qr_parsed->m_state = MR_QR_FPR_OK;
-					qr_parsed->m_id    = mrmailbox_add_or_lookup_contact__(mailbox, NULL, peerstate->m_addr, MR_ORIGIN_UNHANDLED_QR_SCAN, NULL);
-					// TODO: add this to the security log
-				}
-				else {
-					qr_parsed->m_text1 = mr_format_fingerprint(fingerprint);
-					qr_parsed->m_state = MR_QR_FPR_WITHOUT_ADDR;
-				}
-
-			mrsqlite3_unlock(mailbox->m_sql);
-			locked = 0;
-		}
-		else
-		{
-			/* fingerprint and addr set ... */  // TODO: add the states to the security log
-			mrsqlite3_lock(mailbox->m_sql);
-			locked = 1;
-
-				qr_parsed->m_state = MR_QR_FPR_ASK_OOB;
-				qr_parsed->m_id    = mrmailbox_add_or_lookup_contact__(mailbox, name, addr, MR_ORIGIN_UNHANDLED_QR_SCAN, NULL);
-				if( mrapeerstate_load_by_addr__(peerstate, mailbox->m_sql, addr) ) {
-					if( strcasecmp(peerstate->m_fingerprint, fingerprint) != 0 ) {
-						mrmailbox_log_info(mailbox, 0, "Fingerprint mismatch for %s: Scanned: %s, saved: %s", addr, fingerprint, peerstate->m_fingerprint);
-						qr_parsed->m_state = MR_QR_FPR_MISMATCH;
-					}
-				}
-
-				if( qr_parsed->m_state == MR_QR_FPR_ASK_OOB ) {
-					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);
-				}
-
-			mrsqlite3_unlock(mailbox->m_sql);
-			locked = 0;
-		}
-	}
-	else if( addr )
-	{
-        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
-	{
-        qr_parsed->m_state = MR_QR_TEXT;
-		qr_parsed->m_text1 = safe_strdup(qr);
-	}
-
-cleanup:
-	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
-	free(addr);
-	free(fingerprint);
-	mrapeerstate_unref(peerstate);
-	free(payload);
-	free(name);
-	free(random_public);
-	free(random_secret);
-	return qr_parsed;
-}
-
-
 static char* get_self_fingerprint(mrmailbox_t* mailbox)
 {
 	int      locked      = 0;
@@ -370,8 +145,8 @@ static int fingerprint_equals_sender(mrmailbox_t* mailbox, const char* fingerpri
 	int             fingerprint_equal      = 0;
 	int             locked                 = 0;
 	mrarray_t*      contacts               = mrmailbox_get_chat_contacts(mailbox, chat_id);
-	mrcontact_t*    contact                = mrcontact_new();
-	mrapeerstate_t* peerstate              = mrapeerstate_new();
+	mrcontact_t*    contact                = mrcontact_new(mailbox);
+	mrapeerstate_t* peerstate              = mrapeerstate_new(mailbox);
 	char*           fingerprint_normalized = NULL;
 
 	if( mrarray_get_cnt(contacts) != 1 ) {
@@ -404,6 +179,28 @@ cleanup:
 }
 
 
+static int mark_peer_as_verified__(mrmailbox_t* mailbox, const char* fingerprint)
+{
+	int             success = 0;
+	mrapeerstate_t* peerstate = mrapeerstate_new(mailbox);
+
+	if( !mrapeerstate_load_by_fingerprint__(peerstate, mailbox->m_sql, fingerprint) ) {
+		goto cleanup;
+	}
+
+	if( !mrapeerstate_set_verified(peerstate, fingerprint) ) {
+		goto cleanup;
+	}
+
+	mrapeerstate_save_to_db__(peerstate, mailbox->m_sql, 0);
+	success = 1;
+
+cleanup:
+	mrapeerstate_unref(peerstate);
+	return success;
+}
+
+
 static const char* lookup_field(mrmimeparser_t* mimeparser, const char* key)
 {
 	const char* value = NULL;
@@ -423,18 +220,21 @@ static void send_handshake_msg(mrmailbox_t* mailbox, uint32_t chat_id, const cha
 	msg->m_type = MR_MSG_TEXT;
 	msg->m_text = mr_mprintf("Secure-Join: %s", step);
 	msg->m_hidden = 1;
-	mrparam_set_int(msg->m_param, MRP_SYSTEM_CMD,       MR_SYSTEM_OOB_VERIFY_MESSAGE);
-	mrparam_set    (msg->m_param, MRP_SYSTEM_CMD_PARAM, step);
+	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_SYSTEM_CMD_PARAM2, random);
+		mrparam_set(msg->m_param, MRP_CMD_PARAM2, random);
 	}
 
 	if( fingerprint ) {
-		mrparam_set(msg->m_param, MRP_SYSTEM_CMD_PARAM3, fingerprint);
+		mrparam_set(msg->m_param, MRP_CMD_PARAM3, fingerprint);
 	}
 
-	if( strcmp(step, "request") != 0 ) {
+	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
+	}
+	else {
 		mrparam_set_int(msg->m_param, MRP_GUARANTEE_E2EE, 1); /* all but the first message MUST be encrypted */
 	}
 
@@ -444,8 +244,39 @@ static void send_handshake_msg(mrmailbox_t* mailbox, uint32_t chat_id, const cha
 }
 
 
+static void could_not_establish_secure_connection(mrmailbox_t* mailbox, uint32_t chat_id, const char* details)
+{
+	uint32_t     contact_id = chat_id_2_contact_id(mailbox, 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_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)
+
+	free(msg);
+	mrcontact_unref(contact);
+}
+
+
+static void secure_connection_established(mrmailbox_t* mailbox, uint32_t chat_id)
+{
+	uint32_t     contact_id = chat_id_2_contact_id(mailbox, 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);
+
+	// 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);
+
+	free(msg);
+	mrcontact_unref(contact);
+}
+
+
 #define         PLEASE_PROVIDE_RANDOM_SECRET 2
-#define         SECURE_JOIN_BROADCAST        4
+#define         SECUREJOIN_BROADCAST         4
 static int      s_bob_expects = 0;
 
 static mrlot_t* s_bobs_qr_scan = NULL; // should be surround eg. by mrsqlite3_lock/unlock
@@ -463,19 +294,19 @@ static void end_bobs_joining(mrmailbox_t* mailbox, int status)
 
 
 /*******************************************************************************
- * OOB verification main flow
+ * Secure-join main flow
  ******************************************************************************/
 
 
 /**
- * Get QR code text that will offer an oob verification.
+ * Get QR code text that will offer an secure-join verification.
  * The QR code is compatible to the OPENPGP4FPR format so that a basic
  * fingerprint comparison also works eg. with K-9 or OpenKeychain.
  *
  * The scanning Delta Chat device will pass the scanned content to
- * mrmailbox_check_qr() then; if this function reutrns
- * MR_QR_FINGERPRINT_ASK_OOB oob-verification can be joined using
- * mrmailbox_oob_join()
+ * mrmailbox_check_qr() then; if this function returns
+ * MR_QR_ASK_SECUREJOIN an out-of-band-verification can be joined using
+ * mrmailbox_join_securejoin()
  *
  * @memberof mrmailbox_t
  *
@@ -483,7 +314,7 @@ static void end_bobs_joining(mrmailbox_t* mailbox, int status)
  *
  * @return Text that should go to the qr code.
  */
-char* mrmailbox_oob_get_qr(mrmailbox_t* mailbox)
+char* mrmailbox_get_securejoin_qr(mrmailbox_t* mailbox, uint32_t chat_id)
 {
 	/* ==================================
 	   ==== Alice - the inviter side ====
@@ -505,7 +336,7 @@ char* mrmailbox_oob_get_qr(mrmailbox_t* mailbox)
 
 	mrmailbox_ensure_secret_key_exists(mailbox);
 
-	// random_public will be used to allow starting the handshake, random_secret will be used to validate the fingerprint
+	// 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();
 
@@ -545,9 +376,9 @@ cleanup:
 
 
 /**
- * Join an OOB-verification initiated on another device with mrmailbox_oob_get_qr().
+ * 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_FINGERPRINT_ASK_OOB
+ * lot.m_state=MR_QR_ASK_SECUREJOIN
  *
  * 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
@@ -563,7 +394,7 @@ cleanup:
  *     verification successfull, the UI may redirect to the corresponding chat
  *     where a new system message with the state was added.
  */
-int mrmailbox_oob_join(mrmailbox_t* mailbox, const char* qr)
+int mrmailbox_join_securejoin(mrmailbox_t* mailbox, const char* qr)
 {
 	/* =================================
 	   ==== Bob - the joiner's side ====
@@ -583,7 +414,7 @@ int mrmailbox_oob_join(mrmailbox_t* mailbox, const char* qr)
 		goto cleanup;
 	}
 
-	if( ((qr_scan=mrmailbox_check_qr(mailbox, qr))==NULL) || qr_scan->m_state!=MR_QR_FPR_ASK_OOB ) {
+	if( ((qr_scan=mrmailbox_check_qr(mailbox, qr))==NULL) || qr_scan->m_state!=MR_QR_ASK_SECUREJOIN ) {
 		goto cleanup;
 	}
 
@@ -634,22 +465,22 @@ cleanup:
 
 
 /*
- * mrmailbox_oob_is_handshake_message__() should be called called for each
- * incoming mail. if the mail belongs to an oob-verify handshake, the function
+ * 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_oob_handle_handshake_message() then.
+ * processing and call mrmailbox_handle_securejoin_handshake() then.
  */
-int mrmailbox_oob_is_handshake_message__(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser)
+int mrmailbox_is_securejoin_handshake__(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser)
 {
 	if( mailbox == NULL || mimeparser == NULL || lookup_field(mimeparser, "Secure-Join") == NULL ) {
 		return 0;
 	}
 
-	return 1; /* processing is continued in mrmailbox_oob_handle_handshake_message() */
+	return 1; /* processing is continued in mrmailbox_handle_securejoin_handshake() */
 }
 
 
-void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser, uint32_t chat_id)
+void mrmailbox_handle_securejoin_handshake(mrmailbox_t* mailbox, mrmimeparser_t* mimeparser, uint32_t chat_id)
 {
 	int                   locked = 0;
 	const char*           step   = NULL;
@@ -693,7 +524,7 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 
 		mrmailbox_log_info(mailbox, 0, "Secure-join requested.");
 
-		mailbox->m_cb(mailbox, MR_EVENT_SECURE_JOIN_REQUESTED, contact_id, 0);
+		mailbox->m_cb(mailbox, MR_EVENT_SECUREJOIN_REQUESTED, contact_id, 0);
 
 		send_handshake_msg(mailbox, chat_id, "please-provide-random-secret", NULL, NULL); // Alice -> Bob
 	}
@@ -704,7 +535,7 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		   ================================= */
 
 		if( !mimeparser->m_decrypted_and_validated ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (mail not encrypted).");
+			could_not_establish_secure_connection(mailbox, chat_id, "Not encrypted.");
 			end_bobs_joining(mailbox, BOB_ERROR);
 			goto cleanup;
 		}
@@ -721,16 +552,17 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		locked = 0;
 
 		if( !fingerprint_equals_sender(mailbox, scanned_fingerprint_of_alice, chat_id) ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (fingerprint mismatch).");
+			// MitM?
+			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on joiner-side.");
 			end_bobs_joining(mailbox, BOB_ERROR);
 			goto cleanup;
 		}
 
-		mrmailbox_log_info(mailbox, 0, "Fingerprint validated.");
+		mrmailbox_log_info(mailbox, 0, "Fingerprint verified.");
 
 		char* own_fingerprint = get_self_fingerprint(mailbox);
 
-		s_bob_expects = SECURE_JOIN_BROADCAST;
+		s_bob_expects = SECUREJOIN_BROADCAST;
 		send_handshake_msg(mailbox, chat_id, "random-secret", random_secret, own_fingerprint); // Bob -> Alice
 
 		free(own_fingerprint);
@@ -744,28 +576,29 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		   ================================== */
 
 		if( !mimeparser->m_decrypted_and_validated ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (mail not encrypted).");
+			could_not_establish_secure_connection(mailbox, chat_id, "Random-secret not encrypted.");
 			goto cleanup;
 		}
 
 		// verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob
 		const char* fingerprint = NULL;
 		if( (fingerprint=lookup_field(mimeparser, "Secure-Join-Fingerprint")) == NULL ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (fingerprint not provided together with random-secret).");
+			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint not provided.");
 			goto cleanup;
 		}
 
 		if( !fingerprint_equals_sender(mailbox, fingerprint, chat_id) ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (fingerprint mismatch).");
+			// MitM?
+			could_not_establish_secure_connection(mailbox, chat_id, "Fingerprint mismatch on inviter-side.");
 			goto cleanup;
 		}
 
-		mrmailbox_log_info(mailbox, 0, "Fingerprint validated.");
+		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 ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (requested random-secret not provided).");
+			could_not_establish_secure_connection(mailbox, chat_id, "Random-secret not provided.");
 			goto cleanup;
 		}
 
@@ -773,16 +606,26 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		mrsqlite3_lock(mailbox->m_sql);
 		locked = 1;
 			if( lookup_random__(mailbox, "secureJoin.randomSecrets", random_secret) == 0 ) {
-				mrmailbox_log_error(mailbox, 0, "Secure-join failed (random-secret invalid).");
+				mrsqlite3_unlock(mailbox->m_sql);
+				locked = 0;
+				could_not_establish_secure_connection(mailbox, chat_id, "Random-secret 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
 				goto cleanup;
 			}
-			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECURE_INVITED);
+
+			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECUREJOIN_INVITED);
 		mrsqlite3_unlock(mailbox->m_sql);
 		locked = 0;
 
-		mrmailbox_log_info(mailbox, 0, "Random secret validated.");
+		mrmailbox_log_info(mailbox, 0, "Random secret verified.");
 
-		mrmailbox_add_system_msg(mailbox, chat_id, "Secure-join connection established.");
+		secure_connection_established(mailbox, chat_id);
 
 		send_handshake_msg(mailbox, chat_id, "broadcast", NULL, NULL); // Alice -> Bob and all other group members
 	}
@@ -792,13 +635,13 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		   ==== Bob - the joiner's side ====
 		   ================================= */
 
-		if( s_bob_expects != SECURE_JOIN_BROADCAST ) {
+		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( !mimeparser->m_decrypted_and_validated ) {
-			mrmailbox_log_error(mailbox, 0, "Secure-join failed (mail not encrypted).");
+			could_not_establish_secure_connection(mailbox, chat_id, "Broadcast not encrypted.");
 			end_bobs_joining(mailbox, BOB_ERROR);
 			goto cleanup;
 		}
@@ -806,16 +649,35 @@ void mrmailbox_oob_handle_handshake_message(mrmailbox_t* mailbox, mrmimeparser_t
 		uint32_t contact_id = chat_id_2_contact_id(mailbox, chat_id);
 		mrsqlite3_lock(mailbox->m_sql);
 		locked = 1;
-			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECURE_JOINED);
+			if( s_bobs_qr_scan == NULL ) {
+				goto cleanup; // no error, just aborted somehow or a mail from another handshake
+			}
+
+			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
+				goto cleanup;
+			}
+
+			mrmailbox_scaleup_contact_origin__(mailbox, contact_id, MR_ORIGIN_SECUREJOIN_JOINED);
 		mrsqlite3_unlock(mailbox->m_sql);
 		locked = 0;
 
-		mrmailbox_add_system_msg(mailbox, chat_id, "Secure-join connection established.");
+		secure_connection_established(mailbox, chat_id);
 
 		s_bob_expects = 0;
 		end_bobs_joining(mailbox, BOB_SUCCESS);
 	}
 
+	// 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);
+		}
+	}
+
 cleanup:
 	if( locked ) { mrsqlite3_unlock(mailbox->m_sql); }
 }

+ 28 - 31
deltachat-ios/libraries/deltachat-core/src/mrmimefactory.c

@@ -148,9 +148,9 @@ int mrmimefactory_load_msg(mrmimefactory_t* factory, uint32_t msg_id)
 					}
 				}
 
-				int system_command = mrparam_get_int(factory->m_msg->m_param, MRP_SYSTEM_CMD, 0);
-				if( system_command==MR_SYSTEM_MEMBER_REMOVED_FROM_GROUP /* for added members, the list is just fine */) {
-					char* email_to_remove = mrparam_get(factory->m_msg->m_param, MRP_SYSTEM_CMD_PARAM, NULL);
+				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", "");
 					if( email_to_remove && strcasecmp(email_to_remove, self_addr)!=0 )
 					{
@@ -163,8 +163,8 @@ int mrmimefactory_load_msg(mrmimefactory_t* factory, uint32_t msg_id)
 					free(self_addr);
 				}
 
-				if( system_command!=MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE
-				 && system_command!=MR_SYSTEM_OOB_VERIFY_MESSAGE
+				if( command!=MR_CMD_AUTOCRYPT_SETUP_MESSAGE
+				 && command!=MR_CMD_SECUREJOIN_MESSAGE
 				 && mrsqlite3_get_config_int__(mailbox->m_sql, "mdns_enabled", MR_MDNS_DEFAULT_ENABLED) ) {
 					factory->m_req_mdn = 1;
 				}
@@ -239,7 +239,7 @@ cleanup:
 int mrmimefactory_load_mdn(mrmimefactory_t* factory, uint32_t msg_id)
 {
 	int           success = 0, locked = 0;
-	mrcontact_t*  contact = mrcontact_new();
+	mrcontact_t*  contact = mrcontact_new(factory->m_mailbox);
 
 	if( factory == NULL ) {
 		goto cleanup;
@@ -414,7 +414,7 @@ static char* get_subject(const mrchat_t* chat, const mrmsg_t* msg, int afwd_emai
 	char *ret, *raw_subject = mrmsg_get_summarytext_by_raw(msg->m_type, msg->m_text, msg->m_param, APPROX_SUBJECT_CHARS);
 	const char* fwd = afwd_email? "Fwd: " : "";
 
-	if( mrparam_get_int(msg->m_param, MRP_SYSTEM_CMD, 0) == MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE )
+	if( mrparam_get_int(msg->m_param, MRP_CMD, 0) == MR_CMD_AUTOCRYPT_SETUP_MESSAGE )
 	{
 		ret = mrstock_str(MR_STR_AC_SETUP_MSG_SUBJECT); /* do not add the "Chat:" prefix for setup messages */
 	}
@@ -432,7 +432,7 @@ static char* get_subject(const mrchat_t* chat, const mrmsg_t* msg, int afwd_emai
 }
 
 
-int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
+int mrmimefactory_render(mrmimefactory_t* factory)
 {
 	if( factory == NULL
 	 || factory->m_loaded == MR_MF_NOTHING_LOADED
@@ -449,7 +449,7 @@ int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
 	int                          parts = 0;
 	mrmailbox_e2ee_helper_t      e2ee_helper;
 	int                          e2ee_guaranteed = 0;
-	int                          force_unencrypted = 0;
+	int                          force_unencrypted = 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,55 +518,57 @@ int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
 		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);
+
 		/* build header etc. */
-		int system_command = mrparam_get_int(msg->m_param, MRP_SYSTEM_CMD, 0);
+		int command = mrparam_get_int(msg->m_param, MRP_CMD, 0);
 		if( chat->m_type==MR_CHAT_TYPE_GROUP )
 		{
 			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( system_command == MR_SYSTEM_MEMBER_REMOVED_FROM_GROUP ) {
-				char* email_to_remove = mrparam_get(msg->m_param, MRP_SYSTEM_CMD_PARAM, NULL);
+			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( system_command == MR_SYSTEM_MEMBER_ADDED_TO_GROUP ) {
-				char* email_to_add = mrparam_get(msg->m_param, MRP_SYSTEM_CMD_PARAM, NULL);
+			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);
 				}
 			}
-			else if( system_command == MR_SYSTEM_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( system_command == MR_SYSTEM_GROUPIMAGE_CHANGED ) {
-				grpimage = mrparam_get(msg->m_param, MRP_SYSTEM_CMD_PARAM, NULL);
+			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")));
 				}
 			}
 		}
 
-		if( system_command == MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE ) {
+		if( command == MR_CMD_AUTOCRYPT_SETUP_MESSAGE ) {
 			mailimf_fields_add(imf_fields, mailimf_field_new_custom(strdup("Autocrypt-Setup-Message"), strdup("v1")));
 			placeholdertext = mrstock_str(MR_STR_AC_SETUP_MSG_BODY);
-			force_unencrypted = 1;
 		}
 
-		if( system_command == MR_SYSTEM_OOB_VERIFY_MESSAGE ) {
-			char* step = mrparam_get(msg->m_param, MRP_SYSTEM_CMD_PARAM, NULL);
+		if( command == MR_CMD_SECUREJOIN_MESSAGE ) {
+			char* step = mrparam_get(msg->m_param, MRP_CMD_PARAM, NULL);
 			if( step ) {
 				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_SYSTEM_CMD_PARAM2, NULL);
+				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* fingerprint = mrparam_get(msg->m_param, MRP_SYSTEM_CMD_PARAM3, NULL);
+				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*/));
 				}
@@ -646,8 +648,6 @@ int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
 			mailmime_smart_add_part(message, meta_part); /* meta parts are only added if there are other parts */
 			parts++;
 		}
-
-		e2ee_guaranteed = mrparam_get_int(factory->m_msg->m_param, MRP_GUARANTEE_E2EE, 0);
 	}
 	else if( factory->m_loaded == MR_MF_MDN_LOADED )
 	{
@@ -703,7 +703,7 @@ int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
 		- 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 = 1;
+		force_unencrypted = 2;
 	}
 	else
 	{
@@ -724,11 +724,8 @@ int mrmimefactory_render(mrmimefactory_t* factory, int encrypt_to_self)
 	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 ) {
-		if( encrypt_to_self==0 || e2ee_guaranteed ) {
-			/* we're here (1) _always_ on SMTP and (2) on IMAP _only_ if SMTP was encrypted before - otherwise we can save some bytes in not-sending the Autocrypt-header to ourself */
-			mrmailbox_e2ee_encrypt(factory->m_mailbox, factory->m_recipients_addr, e2ee_guaranteed, encrypt_to_self, message, &e2ee_helper);
-		}
+	if( force_unencrypted != 2 ) {
+		mrmailbox_e2ee_encrypt(factory->m_mailbox, factory->m_recipients_addr, force_unencrypted, e2ee_guaranteed, message, &e2ee_helper);
 	}
 
 	if( e2ee_helper.m_encryption_successfull ) {

+ 7 - 7
deltachat-ios/libraries/deltachat-core/src/mrmimefactory.h

@@ -34,12 +34,12 @@ typedef struct _mrchat mrchat_t;
 typedef struct _mrmailbox mrmailbox_t;
 
 
-#define MR_SYSTEM_GROUPNAME_CHANGED           2
-#define MR_SYSTEM_GROUPIMAGE_CHANGED          3
-#define MR_SYSTEM_MEMBER_ADDED_TO_GROUP       4
-#define MR_SYSTEM_MEMBER_REMOVED_FROM_GROUP   5
-#define MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE     6
-#define MR_SYSTEM_OOB_VERIFY_MESSAGE          7
+#define MR_CMD_GROUPNAME_CHANGED           2
+#define MR_CMD_GROUPIMAGE_CHANGED          3
+#define MR_CMD_MEMBER_ADDED_TO_GROUP       4
+#define MR_CMD_MEMBER_REMOVED_FROM_GROUP   5
+#define MR_CMD_AUTOCRYPT_SETUP_MESSAGE     6
+#define MR_CMD_SECUREJOIN_MESSAGE          7
 
 
 typedef enum {
@@ -89,7 +89,7 @@ void        mrmimefactory_init              (mrmimefactory_t*, mrmailbox_t*);
 void        mrmimefactory_empty             (mrmimefactory_t*);
 int         mrmimefactory_load_msg          (mrmimefactory_t*, uint32_t msg_id);
 int         mrmimefactory_load_mdn          (mrmimefactory_t*, uint32_t msg_id);
-int         mrmimefactory_render            (mrmimefactory_t*, int encrypt_to_self);
+int         mrmimefactory_render            (mrmimefactory_t*);
 
 
 #ifdef __cplusplus

+ 3 - 3
deltachat-ios/libraries/deltachat-core/src/mrmimeparser.c

@@ -1422,7 +1422,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) ) {
+	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;
 		}
@@ -1467,7 +1467,7 @@ void mrmimeparser_parse(mrmimeparser_t* ths, const char* body_not_terminated, si
 		}
 		if( has_setup_file ) {
 			/* delete all parts but the application/autocrypt-setup part */
-			ths->m_is_system_message = MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE;
+			ths->m_is_system_message = MR_CMD_AUTOCRYPT_SETUP_MESSAGE;
 			for( i = 0; i < carray_count(ths->m_parts); i++ ) {
 				mrmimepart_t* part = (mrmimepart_t*)carray_get(ths->m_parts, i);
 				if( part->m_int_mimetype!=MR_MIMETYPE_AC_SETUP_FILE ) {
@@ -1560,7 +1560,7 @@ void mrmimeparser_parse(mrmimeparser_t* ths, const char* body_not_terminated, si
 	 && carray_count(ths->m_parts)>=1 ) {
 		mrmimepart_t* textpart = (mrmimepart_t*)carray_get(ths->m_parts, 0);
 		if( textpart->m_type == MR_MSG_TEXT ) {
-			mrparam_set_int(textpart->m_param, MRP_SYSTEM_CMD, MR_SYSTEM_GROUPIMAGE_CHANGED);
+			mrparam_set_int(textpart->m_param, MRP_CMD, MR_CMD_GROUPIMAGE_CHANGED);
 			if( carray_count(ths->m_parts)>=2 ) {
 				mrmimepart_t* imgpart = (mrmimepart_t*)carray_get(ths->m_parts, 1);
 				if( imgpart->m_type == MR_MSG_IMAGE ) {

+ 3 - 0
deltachat-ios/libraries/deltachat-core/src/mrmimeparser.h

@@ -46,6 +46,7 @@ typedef struct mrmimepart_t
 	char*               m_msg_raw;
 	int                 m_bytes;
 	mrparam_t*          m_param;
+
 } mrmimepart_t;
 
 
@@ -76,6 +77,8 @@ typedef struct mrmimeparser_t
 
 	int                    m_is_system_message;
 
+	int                    m_degrade_event;
+
 } mrmimeparser_t;
 
 

+ 21 - 14
deltachat-ios/libraries/deltachat-core/src/mrmsg.c

@@ -136,13 +136,19 @@ uint32_t mrmsg_get_id(const mrmsg_t* msg)
 
 /**
  * Get the ID of contact who wrote the message.
- * To get details about the contact, pass the returned ID to mrmailbox_get_contact().
+ *
+ * If the ID is equal to MR_CONTACT_ID_SELF (1), the message is an outgoing
+ * message that is typically shown on the right side of the chat view.
+ *
+ * Otherwise, the message is an incoming message; to get details about the sender,
+ * pass the returned ID to mrmailbox_get_contact().
  *
  * @memberof mrmsg_t
  *
  * @param msg The message object.
  *
- * @return the ID of the contact who wrote the message, MR_CONTACT_ID_SELF (1) if this is an outgoing message, 0 on errors.
+ * @return the ID of the contact who wrote the message, MR_CONTACT_ID_SELF (1)
+ *     if this is an outgoing message, 0 on errors.
  */
 uint32_t mrmsg_get_from_id(const mrmsg_t* msg)
 {
@@ -744,16 +750,16 @@ int mrmsg_is_forwarded(const mrmsg_t* msg)
 
 
 /**
- * Check if the message is a system command.
- *
- * System command messages are messages not "typed" by the user but
+ * Check if the message is an informational message, created by the
+ * device or by another users. Suche messages are not "typed" by the user but
  * 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,
  * 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.
- * Typically, this text is displayed in another color or in another font than normal user messages.
+ * Typically, these messages are displayed in the center of the chat.
  *
  * @memberof mrmsg_t
  *
@@ -761,15 +767,17 @@ int mrmsg_is_forwarded(const mrmsg_t* msg)
  *
  * @return 1=message is a system command, 0=normal message
  */
-int mrmsg_is_systemcmd(const mrmsg_t* msg)
+int mrmsg_is_info(const mrmsg_t* msg)
 {
 	if( msg == NULL || msg->m_magic != MR_MSG_MAGIC ) {
 		return 0;
 	}
 
-	if( msg->m_from_id == MR_CONTACT_ID_SYSTEM
-	 || msg->m_to_id == MR_CONTACT_ID_SYSTEM
-	 || mrparam_get_int(msg->m_param, MRP_SYSTEM_CMD, 0) ) {
+	int cmd = mrparam_get_int(msg->m_param, MRP_CMD, 0);
+
+	if( msg->m_from_id == MR_CONTACT_ID_DEVICE
+	 || msg->m_to_id == MR_CONTACT_ID_DEVICE
+	 || (cmd && cmd != MR_CMD_AUTOCRYPT_SETUP_MESSAGE) ) {
 		return 1;
 	}
 
@@ -791,8 +799,7 @@ int mrmsg_is_systemcmd(const mrmsg_t* msg)
  * @param msg The message object.
  *
  * @return 1=message is a setup message, 0=no setup message.
- *     For setup messages, mrmsg_is_systemcmd() returns 1 and
- *     mrmsg_get_type() returns MR_MSG_FILE.
+ *     For setup messages, mrmsg_get_type() returns MR_MSG_FILE.
  */
 int mrmsg_is_setupmessage(const mrmsg_t* msg)
 {
@@ -800,7 +807,7 @@ int mrmsg_is_setupmessage(const mrmsg_t* msg)
 		return 0;
 	}
 
-	return mrparam_get_int(msg->m_param, MRP_SYSTEM_CMD, 0)==MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE? 1 : 0;
+	return mrparam_get_int(msg->m_param, MRP_CMD, 0)==MR_CMD_AUTOCRYPT_SETUP_MESSAGE? 1 : 0;
 }
 
 
@@ -1039,7 +1046,7 @@ char* mrmsg_get_summarytext_by_raw(int type, const char* text, mrparam_t* param,
 			break;
 
 		case MR_MSG_FILE:
-			if( mrparam_get_int(param, MRP_SYSTEM_CMD, 0)==MR_SYSTEM_AUTOCRYPT_SETUP_MESSAGE ) {
+			if( mrparam_get_int(param, MRP_CMD, 0)==MR_CMD_AUTOCRYPT_SETUP_MESSAGE ) {
 				ret = mrstock_str(MR_STR_AC_SETUP_MSG_SUBJECT);
 			}
 			else {

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

@@ -91,7 +91,7 @@ char*           mrmsg_get_summarytext       (const mrmsg_t*, int approx_characte
 int             mrmsg_is_sent               (const mrmsg_t*);
 int             mrmsg_is_starred            (const mrmsg_t*);
 int             mrmsg_is_forwarded          (const mrmsg_t*);
-int             mrmsg_is_systemcmd          (const mrmsg_t*);
+int             mrmsg_is_info               (const mrmsg_t*);
 int             mrmsg_is_increation         (const mrmsg_t*);
 
 int             mrmsg_is_setupmessage       (const mrmsg_t*);

+ 6 - 5
deltachat-ios/libraries/deltachat-core/src/mrparam.h

@@ -50,14 +50,15 @@ typedef struct mrparam_t
 #define MRP_MIMETYPE          'm'  /* for msgs */
 #define MRP_AUTHORNAME        'N'  /* for msgs: name of author or artist */
 #define MRP_TRACKNAME         'n'  /* for msgs: name of author or artist */
-#define MRP_GUARANTEE_E2EE    'c'  /* for msgs: 'c'rypted in original/guarantee E2EE or the message is not send */
+#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_WANTS_MDN         'r'  /* for msgs: an incoming message which requestes a MDN (aka read receipt) */
 #define MRP_FORWARDED         'a'  /* for msgs */
-#define MRP_SYSTEM_CMD        'S'  /* for msgs */
-#define MRP_SYSTEM_CMD_PARAM  'E'  /* for msgs */
-#define MRP_SYSTEM_CMD_PARAM2 'F'  /* for msgs */
-#define MRP_SYSTEM_CMD_PARAM3 'G'  /* 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_SERVER_FOLDER     'Z'  /* for jobs */
 #define MRP_SERVER_UID        'z'  /* for jobs */

+ 23 - 15
deltachat-ios/libraries/deltachat-core/src/mrsqlite3.c

@@ -186,7 +186,7 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 						" param TEXT DEFAULT '');");      /* param is for future use, eg. for the status */
 			mrsqlite3_execute__(ths, "CREATE INDEX contacts_index1 ON contacts (name COLLATE NOCASE);"); /* needed for query contacts */
 			mrsqlite3_execute__(ths, "CREATE INDEX contacts_index2 ON contacts (addr COLLATE NOCASE);"); /* needed for query and on receiving mails */
-			mrsqlite3_execute__(ths, "INSERT INTO contacts (id,name,origin) VALUES (1,'self',262144), (2,'system',262144), (3,'rsvd',262144), (4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);");
+			mrsqlite3_execute__(ths, "INSERT INTO contacts (id,name,origin) VALUES (1,'self',262144), (2,'device',262144), (3,'rsvd',262144), (4,'rsvd',262144), (5,'rsvd',262144), (6,'rsvd',262144), (7,'rsvd',262144), (8,'rsvd',262144), (9,'rsvd',262144);");
 			#if !defined(MR_ORIGIN_INTERNAL) || MR_ORIGIN_INTERNAL!=262144
 				#error
 			#endif
@@ -250,8 +250,11 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 			mrsqlite3_set_config_int__(ths, "dbversion", 0);
 		}
 
-		/* Update database */
+		// (1) update low-level database structure.
+		// this should be done before updates that use high-level objects that rely themselves on the low-level structure.
 		int dbversion = mrsqlite3_get_config_int__(ths, "dbversion", 0);
+		int recalc_fingerprints = 0;
+
 		#define NEW_DB_VERSION 1
 			if( dbversion < NEW_DB_VERSION )
 			{
@@ -355,32 +358,37 @@ int mrsqlite3_open__(mrsqlite3_t* ths, const char* dbfile, int flags)
 				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);");
 
-				/* init fingerprint column */
-				sqlite3_stmt* stmt = mrsqlite3_prepare_v2_(ths, "SELECT addr FROM acpeerstates;");
-					while( sqlite3_step(stmt) == SQLITE_ROW ) {
-						mrapeerstate_t* peerstate = mrapeerstate_new();
-							if( mrapeerstate_load_by_addr__(peerstate, ths, (const char*)sqlite3_column_text(stmt, 0))
-							 && mrapeerstate_recalc_fingerprint(peerstate) ) {
-								mrapeerstate_save_to_db__(peerstate, ths, 0/*don't create*/);
-							}
-						mrapeerstate_unref(peerstate);
-					}
-				sqlite3_finalize(stmt);
-
 				dbversion = NEW_DB_VERSION;
 				mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
 			}
 		#undef NEW_DB_VERSION
 
-		#define NEW_DB_VERSION 28
+		#define NEW_DB_VERSION 31
 			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;");
+				recalc_fingerprints = 1;
 
 				dbversion = NEW_DB_VERSION;
 				mrsqlite3_set_config_int__(ths, "dbversion", NEW_DB_VERSION);
 			}
 		#undef NEW_DB_VERSION
+
+		// (2) updates that require high-level objects (the structure is complete now and all objects are usable)
+		if( recalc_fingerprints )
+		{
+			sqlite3_stmt* stmt = mrsqlite3_prepare_v2_(ths, "SELECT addr FROM acpeerstates;");
+				while( sqlite3_step(stmt) == SQLITE_ROW ) {
+					mrapeerstate_t* peerstate = mrapeerstate_new(ths->m_mailbox);
+						if( mrapeerstate_load_by_addr__(peerstate, ths, (const char*)sqlite3_column_text(stmt, 0))
+						 && mrapeerstate_recalc_fingerprint(peerstate) ) {
+							mrapeerstate_save_to_db__(peerstate, ths, 0/*don't create*/);
+						}
+					mrapeerstate_unref(peerstate);
+				}
+			sqlite3_finalize(stmt);
+		}
 	}
 
 	mrmailbox_log_info(ths->m_mailbox, 0, "Opened \"%s\" successfully.", dbfile);

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

@@ -86,6 +86,7 @@ enum
 	,SELECT_a_FROM_chats_contacts_WHERE_i
 	,SELECT_COUNT_FROM_chats_contacts_WHERE_chat_id
 	,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_void_FROM_chats_contacts_WHERE_chat_id_AND_contact_id

+ 0 - 1
deltachat-ios/libraries/deltachat-core/src/mrstock.c

@@ -59,7 +59,6 @@ static char* default_string(int id, int qty)
 		case MR_STR_MSGADDMEMBER:          return safe_strdup("Member %1$s added.");
 		case MR_STR_MSGDELMEMBER:          return safe_strdup("Member %1$s removed.");
 		case MR_STR_MSGGROUPLEFT:          return safe_strdup("Group left.");
-		case MR_STR_ERROR:                 return safe_strdup("Error: %1$s");
 		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.");

+ 0 - 1
deltachat-ios/libraries/deltachat-core/src/mrstock.h

@@ -52,7 +52,6 @@ extern "C" {
 #define MR_STR_MSGADDMEMBER               17
 #define MR_STR_MSGDELMEMBER               18
 #define MR_STR_MSGGROUPLEFT               19
-#define MR_STR_ERROR                      20
 #define MR_STR_SELFNOTINGRP               21
 #define MR_STR_NONETWORK                  22
 #define MR_STR_GIF                        23