diff --git a/cmdline/cmdline.c b/cmdline/cmdline.c index a9f084a5..38d6938b 100644 --- a/cmdline/cmdline.c +++ b/cmdline/cmdline.c @@ -32,6 +32,108 @@ your library */ #include "../src/mrpgp.h" +/* + * Reset database tables. This function is called from Core cmdline. + * + * Argument is a bitmask, executing single or multiple actions in one call. + * + * e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4. + */ +int mrmailbox_reset_tables(mrmailbox_t* ths, int bits) +{ + if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { + return 0; + } + + mrmailbox_log_info(ths, 0, "Resetting tables (%i)...", bits); + + mrsqlite3_lock(ths->m_sql); + + if( bits & 1 ) { + mrsqlite3_execute__(ths->m_sql, "DELETE FROM jobs;"); + mrmailbox_log_info(ths, 0, "(1) Jobs reset."); + } + + if( bits & 2 ) { + mrsqlite3_execute__(ths->m_sql, "DELETE FROM acpeerstates;"); + mrmailbox_log_info(ths, 0, "(2) Peerstates reset."); + } + + if( bits & 4 ) { + mrsqlite3_execute__(ths->m_sql, "DELETE FROM keypairs;"); + mrmailbox_log_info(ths, 0, "(4) Private keypairs reset."); + } + + if( bits & 8 ) { + mrsqlite3_execute__(ths->m_sql, "DELETE FROM contacts WHERE id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) ";"); /* the other IDs are reserved - leave these rows to make sure, the IDs are not used by normal contacts*/ + mrsqlite3_execute__(ths->m_sql, "DELETE FROM chats WHERE id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) ";"); + mrsqlite3_execute__(ths->m_sql, "DELETE FROM chats_contacts;"); + mrsqlite3_execute__(ths->m_sql, "DELETE FROM msgs WHERE id>" MR_STRINGIFY(MR_MSG_ID_LAST_SPECIAL) ";"); + mrsqlite3_execute__(ths->m_sql, "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';"); + mrsqlite3_execute__(ths->m_sql, "DELETE FROM leftgrps;"); + mrmailbox_log_info(ths, 0, "(8) Rest but server config reset."); + } + + mrsqlite3_unlock(ths->m_sql); + + ths->m_cb(ths, MR_EVENT_MSGS_CHANGED, 0, 0); + + return 1; +} + + +/* + * Clean up the contacts table. This function is called from Core cmdline. + * + * All contacts not involved in a chat, not blocked and not being a deaddrop + * are removed. + * + * Deleted contacts from the OS address book normally stay in the contacts + * database. With this cleanup, they are also removed, as well as all + * auto-added contacts, unless they are used in a chat or for blocking purpose. + */ +static int mrmailbox_cleanup_contacts(mrmailbox_t* ths) +{ + if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { + return 0; + } + + mrmailbox_log_info(ths, 0, "Cleaning up contacts ..."); + + mrsqlite3_lock(ths->m_sql); + + mrsqlite3_execute__(ths->m_sql, "DELETE FROM contacts WHERE id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) " AND blocked=0 AND NOT EXISTS (SELECT contact_id FROM chats_contacts where contacts.id = chats_contacts.contact_id) AND NOT EXISTS (select from_id from msgs WHERE msgs.from_id = contacts.id);"); + + mrsqlite3_unlock(ths->m_sql); + + return 1; +} + +static int mrmailbox_poke_eml_file(mrmailbox_t* ths, const char* filename) +{ + /* mainly for testing, may be called by mrmailbox_import_spec() */ + int success = 0; + char* data = NULL; + size_t data_bytes; + + if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { + return 0; + } + + if( mr_read_file(filename, (void**)&data, &data_bytes, ths) == 0 ) { + goto cleanup; + } + + mrmailbox_receive_imf(ths, data, data_bytes, "import", 0, 0); /* this static function is the reason why this function is not moved to mrmailbox_imex.c */ + success = 1; + +cleanup: + free(data); + + return success; +} + + static int poke_public_key(mrmailbox_t* mailbox, const char* addr, const char* public_key_file) { /* mainly for testing: if the partner does not support Autocrypt, diff --git a/deltachat-core.cbp b/deltachat-core.cbp index adc3b914..cef16ce5 100644 --- a/deltachat-core.cbp +++ b/deltachat-core.cbp @@ -492,7 +492,7 @@ - + diff --git a/src/meson.build b/src/meson.build index 75666c22..def26783 100644 --- a/src/meson.build +++ b/src/meson.build @@ -18,7 +18,7 @@ lib_src = [ 'mrmailbox_e2ee.c', 'mrmailbox_imex.c', 'mrmailbox_log.c', - 'mrmailbox_tools.c', + 'mrmailbox_receive_imf.c', 'mrmimefactory.c', 'mrmimeparser.c', 'mrmsg.c', diff --git a/src/mrmailbox-private.h b/src/mrmailbox-private.h index c8870b6e..0352ae3b 100644 --- a/src/mrmailbox-private.h +++ b/src/mrmailbox-private.h @@ -77,20 +77,12 @@ struct _mrmailbox }; - +void mrmailbox_receive_imf (mrmailbox_t*, const char* imf_raw_not_terminated, size_t imf_raw_bytes, const char* server_folder, uint32_t server_uid, uint32_t flags); uint32_t mrmailbox_send_msg_object (mrmailbox_t*, uint32_t chat_id, mrmsg_t*); void mrmailbox_connect_to_imap (mrmailbox_t*, mrjob_t*); void mrmailbox_wake_lock (mrmailbox_t*); void mrmailbox_wake_unlock (mrmailbox_t*); -int mrmailbox_poke_eml_file (mrmailbox_t*, const char* file); -int mrmailbox_is_reply_to_known_message__ (mrmailbox_t*, mrmimeparser_t*); -int mrmailbox_is_reply_to_messenger_message__ (mrmailbox_t*, mrmimeparser_t*); -time_t mrmailbox_correct_bad_timestamp__ (mrmailbox_t* ths, uint32_t chat_id, uint32_t from_id, time_t desired_timestamp, int is_fresh_msg); -void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* ths, struct mailimf_mailbox_list* mb_list, int origin, mrarray_t* ids, int* check_self); -void mrmailbox_add_or_lookup_contacts_by_address_list__(mrmailbox_t* ths, struct mailimf_address_list* adr_list, int origin, mrarray_t* ids, int* check_self); int mrmailbox_get_archived_count__ (mrmailbox_t*); -int mrmailbox_reset_tables (mrmailbox_t*, int bits); /* reset tables but leaves server configuration, 1=jobs, 2=e2ee, 8=rest but server config */ -int mrmailbox_cleanup_contacts (mrmailbox_t* ths); /* remove all contacts that are not used (e.g. in a chat, or blocked */ size_t mrmailbox_get_real_contact_cnt__ (mrmailbox_t*); uint32_t mrmailbox_add_or_lookup_contact__ (mrmailbox_t*, const char* display_name /*can be NULL*/, const char* addr_spec, int origin, int* sth_modified); int mrmailbox_get_contact_origin__ (mrmailbox_t*, uint32_t id, int* ret_blocked); diff --git a/src/mrmailbox.c b/src/mrmailbox.c index b1780973..f085e733 100644 --- a/src/mrmailbox.c +++ b/src/mrmailbox.c @@ -28,7 +28,6 @@ #include "mrmailbox_internal.h" #include "mrimap.h" #include "mrsmtp.h" -#include "mrmimeparser.h" #include "mrmimefactory.h" #include "mrtools.h" #include "mrjob.h" @@ -38,830 +37,6 @@ #include "mrapeerstate.h" -/******************************************************************************* - * Handle groups for received messages - ******************************************************************************/ - - -/* the function tries extracts the group-id from the message and returns the -corresponding chat_id. If the chat_id is not existant, it is created. -If the message contains groups commands (name, profile image, changed members), -they are executed as well. - -So when the function returns, the caller has the group id matching the current -state of the group. */ -static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser, int create_blocked, - int32_t from_id, mrarray_t* to_ids, - uint32_t* ret_chat_id, int* ret_chat_blocked) -{ - uint32_t chat_id = 0; - int chat_blocked = 0; - char* grpid = NULL; - char* grpname = NULL; - sqlite3_stmt* stmt; - int i, to_ids_cnt = mrarray_get_cnt(to_ids); - char* self_addr = NULL; - int recreate_member_list = 0; - int send_EVENT_CHAT_MODIFIED = 0; - - char* X_MrRemoveFromGrp = NULL; /* pointer somewhere into mime_parser, must not be freed */ - char* X_MrAddToGrp = NULL; /* pointer somewhere into mime_parser, must not be freed */ - int X_MrGrpNameChanged = 0; - int X_MrGrpImageChanged = 0; - - /* search the grpid in the header */ - { - struct mailimf_field* field = NULL; - struct mailimf_optional_field* optional_field = NULL; - - if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-ID", "X-MrGrpId"))!=NULL ) { - grpid = safe_strdup(optional_field->fld_value); - } - - if( grpid == NULL ) - { - if( (field=mrmimeparser_lookup_field(mime_parser, "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 ) { - grpid = mr_extract_grpid_from_rfc724_mid(fld_message_id->mid_value); - } - } - - if( grpid == NULL ) - { - if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL && field->fld_type==MAILIMF_FIELD_IN_REPLY_TO ) { - struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; - if( fld_in_reply_to ) { - grpid = mr_extract_grpid_from_rfc724_mid_list(fld_in_reply_to->mid_list); - } - } - - if( grpid == NULL ) - { - if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL && field->fld_type==MAILIMF_FIELD_REFERENCES ) { - struct mailimf_references* fld_references = field->fld_data.fld_references; - if( fld_references ) { - grpid = mr_extract_grpid_from_rfc724_mid_list(fld_references->mid_list); - } - } - - if( grpid == NULL ) - { - goto cleanup; - } - } - } - } - - if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-Name", "X-MrGrpName"))!=NULL ) { - grpname = mr_decode_header_string(optional_field->fld_value); /* this is no changed groupname message */ - } - - 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; - } - 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; - } - 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; - } - 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; - } - } - - /* check, if we have a chat with this group ID */ - stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_CHATS_WHERE_grpid, - "SELECT id, blocked FROM chats WHERE grpid=?;"); - sqlite3_bind_text (stmt, 1, grpid, -1, SQLITE_STATIC); - if( sqlite3_step(stmt)==SQLITE_ROW ) { - chat_id = sqlite3_column_int(stmt, 0); - chat_blocked = sqlite3_column_int(stmt, 1); - } - - /* check if the sender is a member of the existing group - - if not, the message does not go to the group chat but to the normal chat with the sender */ - if( chat_id!=0 && !mrmailbox_is_contact_in_chat__(mailbox, chat_id, from_id) ) { - chat_id = 0; - goto cleanup; - } - - /* check if the group does not exist but should be created */ - int group_explicitly_left = mrmailbox_group_explicitly_left__(mailbox, grpid); - - self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", ""); - if( chat_id == 0 - && !mrmimeparser_is_mailinglist_message(mime_parser) - && grpname - && X_MrRemoveFromGrp==NULL /*otherwise, a pending "quit" message may pop up*/ - && (!group_explicitly_left || (X_MrAddToGrp&&strcasecmp(self_addr,X_MrAddToGrp)==0) ) /*re-create explicitly left groups only if ourself is re-added*/ - ) - { - stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, - "INSERT INTO chats (type, name, grpid, blocked) VALUES(?, ?, ?, ?);"); - sqlite3_bind_int (stmt, 1, MR_CHAT_TYPE_GROUP); - sqlite3_bind_text(stmt, 2, grpname, -1, SQLITE_STATIC); - sqlite3_bind_text(stmt, 3, grpid, -1, SQLITE_STATIC); - sqlite3_bind_int (stmt, 4, create_blocked); - if( sqlite3_step(stmt)!=SQLITE_DONE ) { - goto cleanup; - } - sqlite3_finalize(stmt); - chat_id = sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj); - chat_blocked = create_blocked; - recreate_member_list = 1; - } - - /* again, check chat_id */ - if( chat_id <= MR_CHAT_ID_LAST_SPECIAL ) { - chat_id = 0; - if( group_explicitly_left ) { - chat_id = MR_CHAT_ID_TRASH; /* we got a message for a chat we've deleted - do not show this even as a normal chat */ - } - goto cleanup; - } - - /* execute group commands */ - if( X_MrAddToGrp || X_MrRemoveFromGrp ) - { - recreate_member_list = 1; - } - else if( X_MrGrpNameChanged && grpname && strlen(grpname) < 200 ) - { - stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, "UPDATE chats SET name=? WHERE id=?;"); - sqlite3_bind_text(stmt, 1, grpname, -1, SQLITE_STATIC); - sqlite3_bind_int (stmt, 2, chat_id); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0); - } - - if( X_MrGrpImageChanged ) - { - int ok = 0; - char* grpimage = NULL; - if( carray_count(mime_parser->m_parts)>=1 ) { - mrmimepart_t* textpart = (mrmimepart_t*)carray_get(mime_parser->m_parts, 0); - if( textpart->m_type == MR_MSG_TEXT ) { - if( carray_count(mime_parser->m_parts)>=2 ) { - mrmimepart_t* imgpart = (mrmimepart_t*)carray_get(mime_parser->m_parts, 1); - if( imgpart->m_type == MR_MSG_IMAGE ) { - grpimage = mrparam_get(imgpart->m_param, MRP_FILE, NULL); - ok = 1; - } - } - else { - ok = 1; - } - } - } - - if( ok ) { - mrchat_t* chat = mrchat_new(mailbox); - mrmailbox_log_info(mailbox, 0, "New group image set to %s.", grpimage? "DELETED" : grpimage); - mrchat_load_from_db__(chat, chat_id); - mrparam_set(chat->m_param, MRP_PROFILE_IMAGE, grpimage/*may be NULL*/); - mrchat_update_param__(chat); - mrchat_unref(chat); - free(grpimage); - send_EVENT_CHAT_MODIFIED = 1; - } - } - - /* add members to group/check members - for recreation: we should add a timestamp */ - if( recreate_member_list ) - { - const char* skip = X_MrRemoveFromGrp? X_MrRemoveFromGrp : NULL; - - stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, "DELETE FROM chats_contacts WHERE chat_id=?;"); - sqlite3_bind_int (stmt, 1, chat_id); - sqlite3_step(stmt); - sqlite3_finalize(stmt); - - if( skip==NULL || strcasecmp(self_addr, skip) != 0 ) { - mrmailbox_add_contact_to_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF); - } - - if( from_id > MR_CONTACT_ID_LAST_SPECIAL ) { - if( mrmailbox_contact_addr_equals__(mailbox, from_id, self_addr)==0 - && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, from_id, skip)==0) ) { - mrmailbox_add_contact_to_chat__(mailbox, chat_id, from_id); - } - } - - for( i = 0; i < to_ids_cnt; i++ ) - { - uint32_t to_id = mrarray_get_id(to_ids, i); /* to_id is only once in to_ids and is non-special */ - if( mrmailbox_contact_addr_equals__(mailbox, to_id, self_addr)==0 - && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, to_id, skip)==0) ) { - mrmailbox_add_contact_to_chat__(mailbox, chat_id, to_id); - } - } - send_EVENT_CHAT_MODIFIED = 1; - } - - if( send_EVENT_CHAT_MODIFIED ) { - mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0); - } - - /* check the number of receivers - - the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */ - if( to_ids_cnt == 1 && mime_parser->m_is_send_by_messenger==0 ) { - int is_contact_cnt = mrmailbox_get_chat_contact_count__(mailbox, chat_id); - if( is_contact_cnt > 3 /* to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt. So everything up to 3 is no error. */ ) { - chat_id = 0; - goto cleanup; - } - } - -cleanup: - free(grpid); - free(grpname); - free(self_addr); - if( ret_chat_id ) { *ret_chat_id = chat_id; } - if( ret_chat_blocked ) { *ret_chat_blocked = chat_id? chat_blocked : 0; } -} - - -/******************************************************************************* - * Receive a message and add it to the database - ******************************************************************************/ - - -static void receive_imf(mrmailbox_t* ths, const char* imf_raw_not_terminated, size_t imf_raw_bytes, - const char* server_folder, uint32_t server_uid, uint32_t flags) -{ - /* the function returns the number of created messages in the database */ - int incoming = 0; - int incoming_origin = MR_ORIGIN_UNSET; - #define outgoing (!incoming) - - mrarray_t* to_ids = NULL; - int to_self = 0; - - uint32_t from_id = 0; - int from_id_blocked = 0; - uint32_t to_id = 0; - uint32_t chat_id = 0; - int chat_id_blocked = 0; - int state = MR_STATE_UNDEFINED; - - sqlite3_stmt* stmt; - size_t i, icnt; - uint32_t first_dblocal_id = 0; - char* rfc724_mid = NULL; /* Message-ID from the header */ - time_t message_timestamp = MR_INVALID_TIMESTAMP; - mrmimeparser_t* mime_parser = mrmimeparser_new(ths->m_blobdir, ths); - int db_locked = 0; - int transaction_pending = 0; - const struct mailimf_field* field; - - carray* created_db_entries = carray_new(16); - int create_event_to_send = MR_EVENT_MSGS_CHANGED; - - carray* rr_event_to_send = carray_new(16); - - int has_return_path = 0; - char* txt_raw = NULL; - - mrmailbox_log_info(ths, 0, "Receive message #%lu from %s.", server_uid, server_folder? server_folder:"?"); - - to_ids = mrarray_new(ths, 16); - if( to_ids==NULL || created_db_entries==NULL || rr_event_to_send==NULL || mime_parser == NULL ) { - mrmailbox_log_info(ths, 0, "Bad param."); - goto cleanup; - } - - /* parse the imf to mailimf_message { - mailimf_fields* msg_fields { - clist* fld_list; // list of mailimf_field - } - mailimf_body* msg_body { // != NULL - const char * bd_text; // != NULL - size_t bd_size; - } - }; - normally, this is done by mailimf_message_parse(), however, as we also need the MIME data, - we use mailmime_parse() through MrMimeParser (both call mailimf_struct_multiple_parse() somewhen, I did not found out anything - that speaks against this approach yet) */ - mrmimeparser_parse(mime_parser, imf_raw_not_terminated, imf_raw_bytes); - if( mrhash_count(&mime_parser->m_header)==0 ) { - mrmailbox_log_info(ths, 0, "No header."); - goto cleanup; /* Error - even adding an empty record won't help as we do not know the message ID */ - } - - mrsqlite3_lock(ths->m_sql); - db_locked = 1; - - mrsqlite3_begin_transaction__(ths->m_sql); - transaction_pending = 1; - - - /* Check, if the mail comes from extern, resp. is not sent by us. This is a _really_ important step - as messages sent by us are used to validate other mail senders and receivers. - For this purpose, we assume, the `Return-Path:`-header is never present if the message is sent by us. - The `Received:`-header may be another idea, however, this is also set if mails are transfered from other accounts via IMAP. - Using `From:` alone is no good idea, as mailboxes may use different sending-addresses - moreover, they may change over the years. - However, we use `From:` as an additional hint below. */ - if( mrmimeparser_lookup_field(mime_parser, "Return-Path") ) { - has_return_path = 1; - } - - if( has_return_path ) { - incoming = 1; - } - - /* for incoming messages, get From: and check if it is known (for known From:'s we add the other To:/Cc:/Bcc: in the 3rd pass) */ - if( incoming - && (field=mrmimeparser_lookup_field(mime_parser, "From"))!=NULL - && field->fld_type==MAILIMF_FIELD_FROM) - { - struct mailimf_from* fld_from = field->fld_data.fld_from; - if( fld_from ) - { - int check_self; - mrarray_t* from_list = mrarray_new(ths, 16); - mrmailbox_add_or_lookup_contacts_by_mailbox_list__(ths, fld_from->frm_mb_list, MR_ORIGIN_INCOMING_UNKNOWN_FROM, from_list, &check_self); - if( check_self ) - { - if( mrmimeparser_sender_equals_recipient(mime_parser) ) - { - from_id = MR_CONTACT_ID_SELF; - } - else - { - incoming = 0; /* The `Return-Path:`-approach above works well, however, there may be outgoing messages which we also receive - - for these messages, the `Return-Path:` is set although we're the sender. To correct these cases, we add an - additional From: check - which, however, will not work for older From:-addresses used on the mailbox. */ - } - } - else - { - if( mrarray_get_cnt(from_list)>=1 ) /* if there is no from given, from_id stays 0 which is just fine. These messages are very rare, however, we have to add them to the database (they go to the "deaddrop" chat) to avoid a re-download from the server. See also [**] */ - { - from_id = mrarray_get_id(from_list, 0); - incoming_origin = mrmailbox_get_contact_origin__(ths, from_id, &from_id_blocked); - } - } - mrarray_unref(from_list); - } - } - - /* Make sure, to_ids starts with the first To:-address (Cc: and Bcc: are added in the loop below pass) */ - if( (field=mrmimeparser_lookup_field(mime_parser, "To"))!=NULL - && field->fld_type==MAILIMF_FIELD_TO ) - { - struct mailimf_to* fld_to = field->fld_data.fld_to; /* can be NULL */ - if( fld_to ) - { - mrmailbox_add_or_lookup_contacts_by_address_list__(ths, fld_to->to_addr_list /*!= NULL*/, - outgoing? MR_ORIGIN_OUTGOING_TO : (incoming_origin>=MR_ORIGIN_MIN_VERIFIED? MR_ORIGIN_INCOMING_TO : MR_ORIGIN_INCOMING_UNKNOWN_TO), to_ids, &to_self); - } - } - - if( mrmimeparser_has_nonmeta(mime_parser) ) - { - - /********************************************************************** - * Add parts - *********************************************************************/ - - /* collect the rest information */ - if( (field=mrmimeparser_lookup_field(mime_parser, "Cc"))!=NULL && field->fld_type==MAILIMF_FIELD_CC ) - { - struct mailimf_cc* fld_cc = field->fld_data.fld_cc; - if( fld_cc ) { - mrmailbox_add_or_lookup_contacts_by_address_list__(ths, fld_cc->cc_addr_list, - outgoing? MR_ORIGIN_OUTGOING_CC : (incoming_origin>=MR_ORIGIN_MIN_VERIFIED? MR_ORIGIN_INCOMING_CC : MR_ORIGIN_INCOMING_UNKNOWN_CC), to_ids, NULL); - } - } - - if( (field=mrmimeparser_lookup_field(mime_parser, "Bcc"))!=NULL && field->fld_type==MAILIMF_FIELD_BCC ) - { - struct mailimf_bcc* fld_bcc = field->fld_data.fld_bcc; - if( outgoing && fld_bcc ) { - mrmailbox_add_or_lookup_contacts_by_address_list__(ths, fld_bcc->bcc_addr_list, - MR_ORIGIN_OUTGOING_BCC, to_ids, NULL); - } - } - - /* check if the message introduces a new chat: - - outgoing messages introduce a chat with the first to: address if they are sent by a messenger - - incoming messages introduce a chat only for known contacts if they are sent by a messenger - (of course, the user can add other chats manually later) */ - if( incoming ) - { - state = (flags&MR_IMAP_SEEN)? MR_STATE_IN_SEEN : MR_STATE_IN_FRESH; - to_id = MR_CONTACT_ID_SELF; - - /* test if there is a normal chat with the sender - if so, this allows us to create groups in the next step */ - uint32_t test_normal_chat_id = 0; - int test_normal_chat_id_blocked = 0; - mrmailbox_lookup_real_nchat_by_contact_id__(ths, from_id, &test_normal_chat_id, &test_normal_chat_id_blocked); - - /* get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list, it might also be - blocked and displayed in the deaddrop as a result */ - if( chat_id == 0 ) - { - /* try to create a group */ - int create_blocked = ((test_normal_chat_id&&test_normal_chat_id_blocked==MR_CHAT_NOT_BLOCKED) || incoming_origin>=MR_ORIGIN_MIN_START_NEW_NCHAT/*always false, for now*/)? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; - create_or_lookup_group__(ths, mime_parser, create_blocked, from_id, to_ids, &chat_id, &chat_id_blocked); - if( chat_id && chat_id_blocked && !create_blocked ) { - mrmailbox_unblock_chat__(ths, chat_id); - chat_id_blocked = 0; - } - } - - if( chat_id == 0 ) - { - /* check if the message belongs to a mailing list */ - if( mrmimeparser_is_mailinglist_message(mime_parser) ) { - chat_id = MR_CHAT_ID_TRASH; - mrmailbox_log_info(ths, 0, "Message belongs to a mailing list and is ignored."); - } - } - - if( chat_id == 0 ) - { - /* try to create a normal chat */ - int create_blocked = (incoming_origin>=MR_ORIGIN_MIN_START_NEW_NCHAT/*always false, for now*/ || from_id==to_id)? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; - if( test_normal_chat_id ) { - chat_id = test_normal_chat_id; - chat_id_blocked = test_normal_chat_id_blocked; - } - else { - mrmailbox_create_or_lookup_nchat_by_contact_id__(ths, from_id, create_blocked, &chat_id, &chat_id_blocked); - } - - if( chat_id && chat_id_blocked ) { - if( !create_blocked ) { - mrmailbox_unblock_chat__(ths, chat_id); - chat_id_blocked = 0; - } - else if( mrmailbox_is_reply_to_known_message__(ths, mime_parser) ) { - mrmailbox_scaleup_contact_origin__(ths, from_id, MR_ORIGIN_INCOMING_REPLY_TO); /* we do not want any chat to be created implicitly. Because of the origin-scale-up, the contact requests will pop up and this should be just fine. */ - mrmailbox_log_info(ths, 0, "Message is a reply to a known message, mark sender as known."); - incoming_origin = MR_MAX(incoming_origin, MR_ORIGIN_INCOMING_REPLY_TO); - } - } - } - - if( chat_id == 0 ) - { - /* maybe from_id is null or sth. else is suspicious, move message to trash */ - chat_id = MR_CHAT_ID_TRASH; - } - - /* degrade state for unknown senders and non-delta messages - (the latter may be removed if we run into spam problems, currently this is fine) - (noticed messages do count as being unread; therefore, the deaddrop will not popup in the chatlist) */ - if( chat_id_blocked && state == MR_STATE_IN_FRESH ) { - if( incoming_originm_is_send_by_messenger==0 ) { - state = MR_STATE_IN_NOTICED; - } - } - } - else /* outgoing */ - { - state = MR_STATE_OUT_DELIVERED; /* the mail is on the IMAP server, probably it is also delivered. We cannot recreate other states (read, error). */ - from_id = MR_CONTACT_ID_SELF; - if( mrarray_get_cnt(to_ids) >= 1 ) { - to_id = mrarray_get_id(to_ids, 0); - - if( chat_id == 0 ) - { - create_or_lookup_group__(ths, mime_parser, MR_CHAT_NOT_BLOCKED, from_id, to_ids, &chat_id, &chat_id_blocked); - if( chat_id && chat_id_blocked ) { - mrmailbox_unblock_chat__(ths, chat_id); - chat_id_blocked = 0; - } - } - - if( chat_id == 0 ) - { - int create_blocked = (mime_parser->m_is_send_by_messenger && !mrmailbox_is_contact_blocked__(ths, to_id))? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; - mrmailbox_create_or_lookup_nchat_by_contact_id__(ths, to_id, create_blocked, &chat_id, &chat_id_blocked); - if( chat_id && chat_id_blocked && !create_blocked ) { - mrmailbox_unblock_chat__(ths, chat_id); - chat_id_blocked = 0; - } - } - } - - if( chat_id == 0 ) { - if( mrarray_get_cnt(to_ids) == 0 && to_self ) { - /* from_id == to_id == MR_CONTACT_ID_SELF - this is a self-sent messages, maybe an Autocrypt Setup Message */ - mrmailbox_create_or_lookup_nchat_by_contact_id__(ths, MR_CONTACT_ID_SELF, MR_CHAT_NOT_BLOCKED, &chat_id, &chat_id_blocked); - if( chat_id && chat_id_blocked ) { - mrmailbox_unblock_chat__(ths, chat_id); - chat_id_blocked = 0; - } - } - } - - if( chat_id == 0 ) { - chat_id = MR_CHAT_ID_TRASH; - } - } - - /* correct message_timestamp, it should not be used before, - however, we cannot do this earlier as we need from_id to be set */ - if( (field=mrmimeparser_lookup_field(mime_parser, "Date"))!=NULL && field->fld_type==MAILIMF_FIELD_ORIG_DATE ) { - struct mailimf_orig_date* orig_date = field->fld_data.fld_orig_date; - if( orig_date ) { - message_timestamp = mr_timestamp_from_date(orig_date->dt_date_time); /* is not yet checked against bad times! */ - } - } - message_timestamp = mrmailbox_correct_bad_timestamp__(ths, chat_id, from_id, message_timestamp, (flags&MR_IMAP_SEEN)? 0 : 1 /*fresh message?*/); - - /* unarchive chat */ - mrmailbox_unarchive_chat__(ths, chat_id); - - /* check, if the mail is already in our database - if so, there's nothing more to do - (we may get a mail twice eg. if it is moved between folders) */ - if( (field=mrmimeparser_lookup_field(mime_parser, "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 ) { - rfc724_mid = safe_strdup(fld_message_id->mid_value); - } - } - - if( rfc724_mid == NULL ) { - /* header is lacking a Message-ID - this may be the case, if the message was sent from this account and the mail client - the the SMTP-server set the ID (true eg. for the Webmailer used in all-inkl-KAS) - in these cases, we build a message ID based on some useful header fields that do never change (date, to) - we do not use the folder-local id, as this will change if the mail is moved to another folder. */ - rfc724_mid = mr_create_incoming_rfc724_mid(message_timestamp, from_id, to_ids); - if( rfc724_mid == NULL ) { - mrmailbox_log_info(ths, 0, "Cannot create Message-ID."); - goto cleanup; - } - } - - { - char* old_server_folder = NULL; - uint32_t old_server_uid = 0; - if( mrmailbox_rfc724_mid_exists__(ths, rfc724_mid, &old_server_folder, &old_server_uid) ) { - /* The message is already added to our database; rollback. If needed, update the server_uid which may have changed if the message was moved around on the server. */ - if( strcmp(old_server_folder, server_folder)!=0 || old_server_uid!=server_uid ) { - mrsqlite3_rollback__(ths->m_sql); - transaction_pending = 0; - mrmailbox_update_server_uid__(ths, rfc724_mid, server_folder, server_uid); - } - free(old_server_folder); - mrmailbox_log_info(ths, 0, "Message already in DB."); - goto cleanup; - } - } - - /* if the message is not sent by a messenger, check if it is sent at least as a reply to a messenger message - (later, we move these replies to the Chats-folder) */ - int msgrmsg = mime_parser->m_is_send_by_messenger; /* 1 or 0 for yes/no */ - if( msgrmsg ) - { - mrmailbox_log_info(ths, 0, "Message sent by another messenger (will be moved to Chats-folder)."); - } - else - { - if( mrmailbox_is_reply_to_messenger_message__(ths, mime_parser) ) - { - mrmailbox_log_info(ths, 0, "Message is a reply to a messenger message (will be moved to Chats-folder)."); - msgrmsg = 2; /* 2=no, but is reply to messenger message */ - } - } - - /* 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)) */ - icnt = carray_count(mime_parser->m_parts); /* should be at least one - maybe empty - part */ - for( i = 0; i < icnt; i++ ) - { - mrmimepart_t* part = (mrmimepart_t*)carray_get(mime_parser->m_parts, i); - if( part->m_is_meta ) { - continue; - } - - if( part->m_type == MR_MSG_TEXT ) { - txt_raw = mr_mprintf("%s\n\n%s", mime_parser->m_subject? mime_parser->m_subject : "", part->m_msg_raw); - } - - if( mime_parser->m_is_system_message ) { - mrparam_set_int(part->m_param, MRP_SYSTEM_CMD, mime_parser->m_is_system_message); - } - - stmt = mrsqlite3_predefine__(ths->m_sql, INSERT_INTO_msgs_msscftttsmttpb, - "INSERT INTO msgs (rfc724_mid,server_folder,server_uid,chat_id,from_id, to_id,timestamp,type, state,msgrmsg,txt,txt_raw,param,bytes)" - " VALUES (?,?,?,?,?, ?,?,?, ?,?,?,?,?,?);"); - sqlite3_bind_text (stmt, 1, rfc724_mid, -1, SQLITE_STATIC); - sqlite3_bind_text (stmt, 2, server_folder, -1, SQLITE_STATIC); - sqlite3_bind_int (stmt, 3, server_uid); - sqlite3_bind_int (stmt, 4, chat_id); - sqlite3_bind_int (stmt, 5, from_id); - sqlite3_bind_int (stmt, 6, to_id); - sqlite3_bind_int64(stmt, 7, message_timestamp); - sqlite3_bind_int (stmt, 8, part->m_type); - sqlite3_bind_int (stmt, 9, state); - sqlite3_bind_int (stmt, 10, msgrmsg); - sqlite3_bind_text (stmt, 11, part->m_msg? part->m_msg : "", -1, SQLITE_STATIC); - sqlite3_bind_text (stmt, 12, txt_raw? txt_raw : "", -1, SQLITE_STATIC); - sqlite3_bind_text (stmt, 13, part->m_param->m_packed, -1, SQLITE_STATIC); - sqlite3_bind_int (stmt, 14, part->m_bytes); - if( sqlite3_step(stmt) != SQLITE_DONE ) { - mrmailbox_log_info(ths, 0, "Cannot write DB."); - goto cleanup; /* i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record */ - } - - free(txt_raw); - txt_raw = NULL; - - if( first_dblocal_id == 0 ) { - first_dblocal_id = sqlite3_last_insert_rowid(ths->m_sql->m_cobj); - } - - carray_add(created_db_entries, (void*)(uintptr_t)chat_id, NULL); - carray_add(created_db_entries, (void*)(uintptr_t)first_dblocal_id, NULL); - } - - mrmailbox_log_info(ths, 0, "Message has %i parts and is moved to chat #%i.", icnt, chat_id); - - /* check event to send */ - if( chat_id == MR_CHAT_ID_TRASH ) - { - create_event_to_send = 0; - } - else if( incoming && state==MR_STATE_IN_FRESH ) - { - if( from_id_blocked ) { - create_event_to_send = 0; - } - else if( chat_id_blocked ) { - create_event_to_send = MR_EVENT_MSGS_CHANGED; - /*if( mrsqlite3_get_config_int__(ths->m_sql, "show_deaddrop", 0)!=0 ) { - create_event_to_send = MR_EVENT_INCOMING_MSG; - }*/ - } - else { - create_event_to_send = MR_EVENT_INCOMING_MSG; - } - } - } - - - if( carray_count(mime_parser->m_reports) > 0 ) - { - /****************************************************************** - * Handle reports (mainly MDNs) - *****************************************************************/ - - int mdns_enabled = mrsqlite3_get_config_int__(ths->m_sql, "mdns_enabled", MR_MDNS_DEFAULT_ENABLED); - icnt = carray_count(mime_parser->m_reports); - for( i = 0; i < icnt; i++ ) - { - int mdn_consumed = 0; - struct mailmime* report_root = carray_get(mime_parser->m_reports, i); - struct mailmime_parameter* report_type = mailmime_find_ct_parameter(report_root, "report-type"); - if( report_root==NULL || report_type==NULL || report_type->pa_value==NULL ) { - continue; - } - - if( strcmp(report_type->pa_value, "disposition-notification") == 0 - && clist_count(report_root->mm_data.mm_multipart.mm_mp_list) >= 2 /* the first part is for humans, the second for machines */ ) - { - if( mdns_enabled /*to get a clear functionality, do not show incoming MDNs if the options is disabled*/ ) - { - struct mailmime* report_data = (struct mailmime*)clist_content(clist_next(clist_begin(report_root->mm_data.mm_multipart.mm_mp_list))); - if( report_data - && report_data->mm_content_type->ct_type->tp_type==MAILMIME_TYPE_COMPOSITE_TYPE - && report_data->mm_content_type->ct_type->tp_data.tp_composite_type->ct_type==MAILMIME_COMPOSITE_TYPE_MESSAGE - && strcmp(report_data->mm_content_type->ct_subtype, "disposition-notification")==0 ) - { - /* we received a MDN (although the MDN is only a header, we parse it as a complete mail) */ - const char* report_body = NULL; - size_t report_body_bytes = 0; - char* to_mmap_string_unref = NULL; - if( mailmime_transfer_decode(report_data, &report_body, &report_body_bytes, &to_mmap_string_unref) ) - { - struct mailmime* report_parsed = NULL; - size_t dummy = 0; - if( mailmime_parse(report_body, report_body_bytes, &dummy, &report_parsed)==MAIL_NO_ERROR - && report_parsed!=NULL ) - { - struct mailimf_fields* report_fields = mailmime_find_mailimf_fields(report_parsed); - if( report_fields ) - { - struct mailimf_optional_field* of_disposition = mailimf_find_optional_field(report_fields, "Disposition"); /* MUST be preset, _if_ preset, we assume a sort of attribution and do not go into details */ - struct mailimf_optional_field* of_org_msgid = mailimf_find_optional_field(report_fields, "Original-Message-ID"); /* can't live without */ - if( of_disposition && of_disposition->fld_value && of_org_msgid && of_org_msgid->fld_value ) - { - char* rfc724_mid = NULL; - dummy = 0; - if( mailimf_msg_id_parse(of_org_msgid->fld_value, strlen(of_org_msgid->fld_value), &dummy, &rfc724_mid)==MAIL_NO_ERROR - && rfc724_mid!=NULL ) - { - uint32_t chat_id = 0; - uint32_t msg_id = 0; - if( mrmailbox_mdn_from_ext__(ths, from_id, rfc724_mid, &chat_id, &msg_id) ) { - carray_add(rr_event_to_send, (void*)(uintptr_t)chat_id, NULL); - carray_add(rr_event_to_send, (void*)(uintptr_t)msg_id, NULL); - } - mdn_consumed = (msg_id!=0); - free(rfc724_mid); - } - } - } - mailmime_free(report_parsed); - } - - if( to_mmap_string_unref ) { mmap_string_unref(to_mmap_string_unref); } - } - } - } - - /* Move the MDN away to the chats folder. We do this for: - - Consumed or not consumed MDNs from other messengers - - Consumed MDNs from normal MUAs - Unconsumed MDNs from normal MUAs are _not_ moved. - NB: we do not delete the MDN as it may be used by other clients - - CAVE: we rely on mrimap_markseen_msg() not to move messages that are already in the correct folder. - otherwise, the moved message get a new server_uid and is "fresh" again and we will be here again to move it away - - 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__(ths, MRJ_MARKSEEN_MDN_ON_IMAP, 0, jobparam); - free(jobparam); - } - } - - } /* for() */ - - } - - /* debug print? */ - if( mrsqlite3_get_config_int__(ths->m_sql, "save_eml", 0) ) { - char* emlname = mr_mprintf("%s/%s-%i.eml", ths->m_blobdir, server_folder, (int)first_dblocal_id /*may be 0 for MDNs*/); - FILE* emlfileob = fopen(emlname, "w"); - if( emlfileob ) { - fwrite(imf_raw_not_terminated, 1, imf_raw_bytes, emlfileob); - fclose(emlfileob); - } - free(emlname); - } - - /* end sql-transaction */ - mrsqlite3_commit__(ths->m_sql); - transaction_pending = 0; - - /* done */ -cleanup: - if( transaction_pending ) { - mrsqlite3_rollback__(ths->m_sql); - } - - if( db_locked ) { - mrsqlite3_unlock(ths->m_sql); - } - - if( mime_parser ) { - mrmimeparser_unref(mime_parser); - } - - if( rfc724_mid ) { - free(rfc724_mid); - } - - if( to_ids ) { - mrarray_unref(to_ids); - } - - if( created_db_entries ) { - if( create_event_to_send ) { - size_t i, icnt = carray_count(created_db_entries); - for( i = 0; i < icnt; i += 2 ) { - ths->m_cb(ths, create_event_to_send, (uintptr_t)carray_get(created_db_entries, i), (uintptr_t)carray_get(created_db_entries, i+1)); - } - } - carray_free(created_db_entries); - } - - if( rr_event_to_send ) { - size_t i, icnt = carray_count(rr_event_to_send); - for( i = 0; i < icnt; i += 2 ) { - ths->m_cb(ths, MR_EVENT_MSG_READ, (uintptr_t)carray_get(rr_event_to_send, i), (uintptr_t)carray_get(rr_event_to_send, i+1)); - } - carray_free(rr_event_to_send); - } - - free(txt_raw); -} - - /******************************************************************************* * Main interface ******************************************************************************/ @@ -889,7 +64,7 @@ static void cb_set_config(mrimap_t* imap, const char* key, const char* value) static void cb_receive_imf(mrimap_t* imap, const char* imf_raw_not_terminated, size_t imf_raw_bytes, const char* server_folder, uint32_t server_uid, uint32_t flags) { mrmailbox_t* mailbox = (mrmailbox_t*)imap->m_userData; - receive_imf(mailbox, imf_raw_not_terminated, imf_raw_bytes, server_folder, server_uid, flags); + mrmailbox_receive_imf(mailbox, imf_raw_not_terminated, imf_raw_bytes, server_folder, server_uid, flags); } @@ -1188,31 +363,6 @@ char* mrmailbox_get_blobdir(mrmailbox_t* mailbox) } -int mrmailbox_poke_eml_file(mrmailbox_t* ths, const char* filename) -{ - /* mainly for testing, may be called by mrmailbox_import_spec() */ - int success = 0; - char* data = NULL; - size_t data_bytes; - - if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { - return 0; - } - - if( mr_read_file(filename, (void**)&data, &data_bytes, ths) == 0 ) { - goto cleanup; - } - - receive_imf(ths, data, data_bytes, "import", 0, 0); /* this static function is the reason why this function is not moved to mrmailbox_imex.c */ - success = 1; - -cleanup: - free(data); - - return success; -} - - /******************************************************************************* * INI-handling, Information ******************************************************************************/ @@ -1496,86 +646,6 @@ int mrmailbox_get_archived_count__(mrmailbox_t* mailbox) } -/** - * Reset database tables. This function is called from Core cmdline. - * - * Argument is a bitmask, executing single or multiple actions in one call. - * - * e.g. bitmask 7 triggers actions definded with bits 1, 2 and 4. - */ -int mrmailbox_reset_tables(mrmailbox_t* ths, int bits) -{ - if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { - return 0; - } - - mrmailbox_log_info(ths, 0, "Resetting tables (%i)...", bits); - - mrsqlite3_lock(ths->m_sql); - - if( bits & 1 ) { - mrsqlite3_execute__(ths->m_sql, "DELETE FROM jobs;"); - mrmailbox_log_info(ths, 0, "(1) Jobs reset."); - } - - if( bits & 2 ) { - mrsqlite3_execute__(ths->m_sql, "DELETE FROM acpeerstates;"); - mrmailbox_log_info(ths, 0, "(2) Peerstates reset."); - } - - if( bits & 4 ) { - mrsqlite3_execute__(ths->m_sql, "DELETE FROM keypairs;"); - mrmailbox_log_info(ths, 0, "(4) Private keypairs reset."); - } - - if( bits & 8 ) { - mrsqlite3_execute__(ths->m_sql, "DELETE FROM contacts WHERE id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) ";"); /* the other IDs are reserved - leave these rows to make sure, the IDs are not used by normal contacts*/ - mrsqlite3_execute__(ths->m_sql, "DELETE FROM chats WHERE id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) ";"); - mrsqlite3_execute__(ths->m_sql, "DELETE FROM chats_contacts;"); - mrsqlite3_execute__(ths->m_sql, "DELETE FROM msgs WHERE id>" MR_STRINGIFY(MR_MSG_ID_LAST_SPECIAL) ";"); - mrsqlite3_execute__(ths->m_sql, "DELETE FROM config WHERE keyname LIKE 'imap.%' OR keyname LIKE 'configured%';"); - mrsqlite3_execute__(ths->m_sql, "DELETE FROM leftgrps;"); - mrmailbox_log_info(ths, 0, "(8) Rest but server config reset."); - } - - update_config_cache__(ths, NULL); - - mrsqlite3_unlock(ths->m_sql); - - ths->m_cb(ths, MR_EVENT_MSGS_CHANGED, 0, 0); - - return 1; -} - -/** - * Clean up the contacts table. This function is called from Core cmdline. - * - * All contacts not involved in a chat, not blocked and not being a deaddrop - * are removed. - * - * Deleted contacts from the OS address book normally stay in the contacts - * database. With this cleanup, they are also removed, as well as all - * auto-added contacts, unless they are used in a chat or for blocking purpose. - * - */ -int mrmailbox_cleanup_contacts(mrmailbox_t* ths) -{ - if( ths == NULL || ths->m_magic != MR_MAILBOX_MAGIC ) { - return 0; - } - - mrmailbox_log_info(ths, 0, "Cleaning up contacts ..."); - - mrsqlite3_lock(ths->m_sql); - - mrsqlite3_execute__(ths->m_sql, "DELETE FROM contacts WHERE id>" MR_STRINGIFY(MR_CONTACT_ID_LAST_SPECIAL) " AND blocked=0 AND NOT EXISTS (SELECT contact_id FROM chats_contacts where contacts.id = chats_contacts.contact_id) AND NOT EXISTS (select from_id from msgs WHERE msgs.from_id = contacts.id);"); - - mrsqlite3_unlock(ths->m_sql); - - return 1; -} - - /** * Find out the version of the Delta Chat core library. * @@ -2217,7 +1287,6 @@ cleanup: } - /** * Returns the message IDs of all _fresh_ messages of any chat. Typically used for implementing * notification summaries. The result must be free()'d. @@ -2575,11 +1644,6 @@ int mrchat_set_draft(mrchat_t* chat, const char* msg) /* deprecated */ } - -#define IS_SELF_IN_GROUP__ (mrmailbox_is_contact_in_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF)==1) -#define DO_SEND_STATUS_MAILS (mrparam_get_int(chat->m_param, MRP_UNPROMOTED, 0)==0) - - int mrmailbox_get_fresh_msg_count__(mrmailbox_t* mailbox, uint32_t chat_id) { sqlite3_stmt* stmt = NULL; @@ -2744,19 +1808,10 @@ void mrmailbox_create_or_lookup_nchat_by_contact_id__(mrmailbox_t* mailbox, uint sqlite3_finalize(stmt); stmt = NULL; - /* cleanup */ cleanup: - if( q ) { - sqlite3_free(q); - } - - if( stmt ) { - sqlite3_finalize(stmt); - } - - if( contact ) { - mrcontact_unref(contact); - } + if( q ) { sqlite3_free(q); } + if( stmt ) { sqlite3_finalize(stmt); } + if( contact ) { mrcontact_unref(contact); } if( ret_chat_id ) { *ret_chat_id = chat_id; } if( ret_chat_blocked ) { *ret_chat_blocked = create_blocked; } @@ -2874,20 +1929,12 @@ void mrmailbox_archive_chat(mrmailbox_t* mailbox, uint32_t chat_id, int archive) ******************************************************************************/ -/* _If_ deleting a group chat would implies to leave the group, things get complicated -as this would require to send a message before the chat is deleted physically. -To make things even more complicated, there may be other chat messages waiting to be send. -We used the following approach: -1. If we do not need to send a message, we delete the chat directly -2. If we need to send a message, we set chats.blocked=1 and add the parameter - MRP_DEL_AFTER_SEND with a random value to both, the last message to be sent and to the - chat (we would use msg_id, however, we may not get this in time) -3. When the message with the MRP_DEL_AFTER_SEND-value of the chat was sent to IMAP, we physically - delete the chat. -However, from 2017-11-02, we do not implicitly leave the group as this results in different behaviours to normal -chat and _only_ leaving a group is also a valid usecase. */ + + +#define IS_SELF_IN_GROUP__ (mrmailbox_is_contact_in_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF)==1) +#define DO_SEND_STATUS_MAILS (mrparam_get_int(chat->m_param, MRP_UNPROMOTED, 0)==0) int mrmailbox_delete_chat_part2(mrmailbox_t* mailbox, uint32_t chat_id) diff --git a/src/mrmailbox_receive_imf.c b/src/mrmailbox_receive_imf.c new file mode 100644 index 00000000..db025536 --- /dev/null +++ b/src/mrmailbox_receive_imf.c @@ -0,0 +1,1119 @@ +/******************************************************************************* + * + * Delta Chat Core + * Copyright (C) 2017 Björn Petersen + * Contact: r10s@b44t.com, http://b44t.com + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see http://www.gnu.org/licenses/ . + * + ******************************************************************************/ + + +#include "mrmailbox_internal.h" +#include "mrmimeparser.h" +#include "mrmimefactory.h" +#include "mrimap.h" +#include "mrjob.h" + + +/******************************************************************************* + * Add contacts to database on receiving messages + ******************************************************************************/ + + +static void add_or_lookup_contact_by_addr__(mrmailbox_t* mailbox, const char* display_name_enc, const char* addr_spec, int origin, mrarray_t* ids, int* check_self) +{ + /* is addr_spec equal to SELF? */ + int dummy; + if( check_self == NULL ) { check_self = &dummy; } + + *check_self = 0; + + char* self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", ""); + if( strcasecmp(self_addr, addr_spec)==0 ) { + *check_self = 1; + } + free(self_addr); + + if( *check_self ) { + return; + } + + /* add addr_spec if missing, update otherwise */ + char* display_name_dec = NULL; + if( display_name_enc ) { + display_name_dec = mr_decode_header_string(display_name_enc); + mr_normalize_name(display_name_dec); + } + + uint32_t row_id = mrmailbox_add_or_lookup_contact__(mailbox, display_name_dec /*can be NULL*/, addr_spec, origin, NULL); + + free(display_name_dec); + + if( row_id ) { + if( !mrarray_search_id(ids, row_id, NULL) ) { + mrarray_add_id(ids, row_id); + } + } +} + + +static void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* mailbox, struct mailimf_mailbox_list* mb_list, int origin, mrarray_t* ids, int* check_self) +{ + clistiter* cur; + for( cur = clist_begin(mb_list->mb_list); cur!=NULL ; cur=clist_next(cur) ) { + struct mailimf_mailbox* mb = (struct mailimf_mailbox*)clist_content(cur); + if( mb ) { + add_or_lookup_contact_by_addr__(mailbox, mb->mb_display_name, mb->mb_addr_spec, origin, ids, check_self); + } + } +} + + +static void mrmailbox_add_or_lookup_contacts_by_address_list__(mrmailbox_t* mailbox, struct mailimf_address_list* adr_list, int origin, mrarray_t* ids, int* check_self) +{ + clistiter* cur; + for( cur = clist_begin(adr_list->ad_list); cur!=NULL ; cur=clist_next(cur) ) { + struct mailimf_address* adr = (struct mailimf_address*)clist_content(cur); + if( adr ) { + if( adr->ad_type == MAILIMF_ADDRESS_MAILBOX ) { + struct mailimf_mailbox* mb = adr->ad_data.ad_mailbox; /* can be NULL */ + if( mb ) { + add_or_lookup_contact_by_addr__(mailbox, mb->mb_display_name, mb->mb_addr_spec, origin, ids, check_self); + } + } + else if( adr->ad_type == MAILIMF_ADDRESS_GROUP ) { + struct mailimf_group* group = adr->ad_data.ad_group; /* can be NULL */ + if( group && group->grp_mb_list /*can be NULL*/ ) { + mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mailbox, group->grp_mb_list, origin, ids, check_self); + } + } + } + } +} + + +/******************************************************************************* + * Check if a message is a reply to a known message (messenger or non-messenger) + ******************************************************************************/ + + +static int is_known_rfc724_mid__(mrmailbox_t* mailbox, const char* rfc724_mid) +{ + if( rfc724_mid ) { + sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_msgs_WHERE_cm, + "SELECT m.id FROM msgs m " + " LEFT JOIN chats c ON m.chat_id=c.id " + " WHERE m.rfc724_mid=? " + " AND m.chat_id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) + " AND c.blocked=0;"); + sqlite3_bind_text(stmt, 1, rfc724_mid, -1, SQLITE_STATIC); + if( sqlite3_step(stmt) == SQLITE_ROW ) { + return 1; + } + } + return 0; +} + + +static int is_known_rfc724_mid_in_list__(mrmailbox_t* mailbox, const clist* mid_list) +{ + if( mid_list ) { + clistiter* cur; + for( cur = clist_begin(mid_list); cur!=NULL ; cur=clist_next(cur) ) { + if( is_known_rfc724_mid__(mailbox, clist_content(cur)) ) { + return 1; + } + } + } + + return 0; +} + + +static int mrmailbox_is_reply_to_known_message__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser) +{ + /* check if the message is a reply to a known message; the replies are identified by the Message-ID from + `In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in mrchat.c) */ + + struct mailimf_optional_field* optional_field; + if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Predecessor", "X-MrPredecessor")) != NULL ) + { + if( is_known_rfc724_mid__(mailbox, optional_field->fld_value) ) { + return 1; + } + } + + struct mailimf_field* field; + if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL + && field->fld_type == MAILIMF_FIELD_IN_REPLY_TO ) + { + struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; + if( fld_in_reply_to ) { + if( is_known_rfc724_mid_in_list__(mailbox, field->fld_data.fld_in_reply_to->mid_list) ) { + return 1; + } + } + } + + if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL + && field->fld_type == MAILIMF_FIELD_REFERENCES ) + { + struct mailimf_references* fld_references = field->fld_data.fld_references; + if( fld_references ) { + if( is_known_rfc724_mid_in_list__(mailbox, field->fld_data.fld_references->mid_list) ) { + return 1; + } + } + } + + return 0; +} + + +/******************************************************************************* + * Check if a message is a reply to any messenger message + ******************************************************************************/ + + +static int is_msgrmsg_rfc724_mid__(mrmailbox_t* mailbox, const char* rfc724_mid) +{ + if( rfc724_mid ) { + sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_msgs_WHERE_mcm, + "SELECT id FROM msgs " + " WHERE rfc724_mid=? " + " AND msgrmsg!=0 " + " AND chat_id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) ";"); + sqlite3_bind_text(stmt, 1, rfc724_mid, -1, SQLITE_STATIC); + if( sqlite3_step(stmt) == SQLITE_ROW ) { + return 1; + } + } + return 0; +} + + +static int is_msgrmsg_rfc724_mid_in_list__(mrmailbox_t* mailbox, const clist* mid_list) +{ + if( mid_list ) { + clistiter* cur; + for( cur = clist_begin(mid_list); cur!=NULL ; cur=clist_next(cur) ) { + if( is_msgrmsg_rfc724_mid__(mailbox, clist_content(cur)) ) { + return 1; + } + } + } + + return 0; +} + + +static int mrmailbox_is_reply_to_messenger_message__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser) +{ + /* function checks, if the message defined by mime_parser references a message send by us from Delta Chat. + + This is similar to is_reply_to_known_message__() but + - checks also if any of the referenced IDs are send by a messenger + - it is okay, if the referenced messages are moved to trash here + - no check for the Chat-* headers (function is only called if it is no messenger message itself) */ + + struct mailimf_field* field; + if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL + && field->fld_type==MAILIMF_FIELD_IN_REPLY_TO ) + { + struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; + if( fld_in_reply_to ) { + if( is_msgrmsg_rfc724_mid_in_list__(mailbox, field->fld_data.fld_in_reply_to->mid_list) ) { + return 1; + } + } + } + + if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL + && field->fld_type==MAILIMF_FIELD_REFERENCES ) + { + struct mailimf_references* fld_references = field->fld_data.fld_references; + if( fld_references ) { + if( is_msgrmsg_rfc724_mid_in_list__(mailbox, field->fld_data.fld_references->mid_list) ) { + return 1; + } + } + } + + return 0; +} + + +/******************************************************************************* + * Misc. Tools + ******************************************************************************/ + + +static time_t mrmailbox_correct_bad_timestamp__(mrmailbox_t* mailbox, uint32_t chat_id, uint32_t from_id, time_t desired_timestamp, int is_fresh_msg) +{ + /* used for correcting timestamps of _received_ messages. + use the last message from another user (including SELF) as the MINIMUM + (we do this check only for fresh messages, other messages may pop up whereever, this may happen eg. when restoring old messages or synchronizing different clients) */ + if( is_fresh_msg ) + { + sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_timestamp_FROM_msgs_WHERE_timestamp, + "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?"); + sqlite3_bind_int (stmt, 1, chat_id); + sqlite3_bind_int (stmt, 2, from_id); + sqlite3_bind_int64(stmt, 3, desired_timestamp); + if( sqlite3_step(stmt)==SQLITE_ROW ) + { + time_t last_msg_time = sqlite3_column_int64(stmt, 0); + if( last_msg_time > 0 /* may happen as we do not check against sqlite3_column_type()!=SQLITE_NULL */ ) { + if( desired_timestamp <= last_msg_time ) { + desired_timestamp = last_msg_time+1; /* this may result in several incoming messages having the same + one-second-after-the-last-other-message-timestamp. however, this is no big deal + as we do not try to recrete the order of bad-date-messages and as we always order by ID as second criterion */ + } + } + } + } + + /* use the (smeared) current time as the MAXIMUM */ + if( desired_timestamp >= mr_smeared_time__() ) + { + desired_timestamp = mr_create_smeared_timestamp__(); + } + + return desired_timestamp; +} + + +/******************************************************************************* + * Handle groups for received messages + ******************************************************************************/ + + +/* the function tries extracts the group-id from the message and returns the +corresponding chat_id. If the chat_id is not existant, it is created. +If the message contains groups commands (name, profile image, changed members), +they are executed as well. + +So when the function returns, the caller has the group id matching the current +state of the group. */ +static void create_or_lookup_group__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser, int create_blocked, + int32_t from_id, mrarray_t* to_ids, + uint32_t* ret_chat_id, int* ret_chat_blocked) +{ + uint32_t chat_id = 0; + int chat_blocked = 0; + char* grpid = NULL; + char* grpname = NULL; + sqlite3_stmt* stmt; + int i, to_ids_cnt = mrarray_get_cnt(to_ids); + char* self_addr = NULL; + int recreate_member_list = 0; + int send_EVENT_CHAT_MODIFIED = 0; + + char* X_MrRemoveFromGrp = NULL; /* pointer somewhere into mime_parser, must not be freed */ + char* X_MrAddToGrp = NULL; /* pointer somewhere into mime_parser, must not be freed */ + int X_MrGrpNameChanged = 0; + int X_MrGrpImageChanged = 0; + + /* search the grpid in the header */ + { + struct mailimf_field* field = NULL; + struct mailimf_optional_field* optional_field = NULL; + + if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-ID", "X-MrGrpId"))!=NULL ) { + grpid = safe_strdup(optional_field->fld_value); + } + + if( grpid == NULL ) + { + if( (field=mrmimeparser_lookup_field(mime_parser, "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 ) { + grpid = mr_extract_grpid_from_rfc724_mid(fld_message_id->mid_value); + } + } + + if( grpid == NULL ) + { + if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL && field->fld_type==MAILIMF_FIELD_IN_REPLY_TO ) { + struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; + if( fld_in_reply_to ) { + grpid = mr_extract_grpid_from_rfc724_mid_list(fld_in_reply_to->mid_list); + } + } + + if( grpid == NULL ) + { + if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL && field->fld_type==MAILIMF_FIELD_REFERENCES ) { + struct mailimf_references* fld_references = field->fld_data.fld_references; + if( fld_references ) { + grpid = mr_extract_grpid_from_rfc724_mid_list(fld_references->mid_list); + } + } + + if( grpid == NULL ) + { + goto cleanup; + } + } + } + } + + if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Group-Name", "X-MrGrpName"))!=NULL ) { + grpname = mr_decode_header_string(optional_field->fld_value); /* this is no changed groupname message */ + } + + 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; + } + 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; + } + 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; + } + 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; + } + } + + /* check, if we have a chat with this group ID */ + stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_CHATS_WHERE_grpid, + "SELECT id, blocked FROM chats WHERE grpid=?;"); + sqlite3_bind_text (stmt, 1, grpid, -1, SQLITE_STATIC); + if( sqlite3_step(stmt)==SQLITE_ROW ) { + chat_id = sqlite3_column_int(stmt, 0); + chat_blocked = sqlite3_column_int(stmt, 1); + } + + /* check if the sender is a member of the existing group - + if not, the message does not go to the group chat but to the normal chat with the sender */ + if( chat_id!=0 && !mrmailbox_is_contact_in_chat__(mailbox, chat_id, from_id) ) { + chat_id = 0; + goto cleanup; + } + + /* check if the group does not exist but should be created */ + int group_explicitly_left = mrmailbox_group_explicitly_left__(mailbox, grpid); + + self_addr = mrsqlite3_get_config__(mailbox->m_sql, "configured_addr", ""); + if( chat_id == 0 + && !mrmimeparser_is_mailinglist_message(mime_parser) + && grpname + && X_MrRemoveFromGrp==NULL /*otherwise, a pending "quit" message may pop up*/ + && (!group_explicitly_left || (X_MrAddToGrp&&strcasecmp(self_addr,X_MrAddToGrp)==0) ) /*re-create explicitly left groups only if ourself is re-added*/ + ) + { + stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, + "INSERT INTO chats (type, name, grpid, blocked) VALUES(?, ?, ?, ?);"); + sqlite3_bind_int (stmt, 1, MR_CHAT_TYPE_GROUP); + sqlite3_bind_text(stmt, 2, grpname, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, grpid, -1, SQLITE_STATIC); + sqlite3_bind_int (stmt, 4, create_blocked); + if( sqlite3_step(stmt)!=SQLITE_DONE ) { + goto cleanup; + } + sqlite3_finalize(stmt); + chat_id = sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj); + chat_blocked = create_blocked; + recreate_member_list = 1; + } + + /* again, check chat_id */ + if( chat_id <= MR_CHAT_ID_LAST_SPECIAL ) { + chat_id = 0; + if( group_explicitly_left ) { + chat_id = MR_CHAT_ID_TRASH; /* we got a message for a chat we've deleted - do not show this even as a normal chat */ + } + goto cleanup; + } + + /* execute group commands */ + if( X_MrAddToGrp || X_MrRemoveFromGrp ) + { + recreate_member_list = 1; + } + else if( X_MrGrpNameChanged && grpname && strlen(grpname) < 200 ) + { + stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, "UPDATE chats SET name=? WHERE id=?;"); + sqlite3_bind_text(stmt, 1, grpname, -1, SQLITE_STATIC); + sqlite3_bind_int (stmt, 2, chat_id); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0); + } + + if( X_MrGrpImageChanged ) + { + int ok = 0; + char* grpimage = NULL; + if( carray_count(mime_parser->m_parts)>=1 ) { + mrmimepart_t* textpart = (mrmimepart_t*)carray_get(mime_parser->m_parts, 0); + if( textpart->m_type == MR_MSG_TEXT ) { + if( carray_count(mime_parser->m_parts)>=2 ) { + mrmimepart_t* imgpart = (mrmimepart_t*)carray_get(mime_parser->m_parts, 1); + if( imgpart->m_type == MR_MSG_IMAGE ) { + grpimage = mrparam_get(imgpart->m_param, MRP_FILE, NULL); + ok = 1; + } + } + else { + ok = 1; + } + } + } + + if( ok ) { + mrchat_t* chat = mrchat_new(mailbox); + mrmailbox_log_info(mailbox, 0, "New group image set to %s.", grpimage? "DELETED" : grpimage); + mrchat_load_from_db__(chat, chat_id); + mrparam_set(chat->m_param, MRP_PROFILE_IMAGE, grpimage/*may be NULL*/); + mrchat_update_param__(chat); + mrchat_unref(chat); + free(grpimage); + send_EVENT_CHAT_MODIFIED = 1; + } + } + + /* add members to group/check members + for recreation: we should add a timestamp */ + if( recreate_member_list ) + { + const char* skip = X_MrRemoveFromGrp? X_MrRemoveFromGrp : NULL; + + stmt = mrsqlite3_prepare_v2_(mailbox->m_sql, "DELETE FROM chats_contacts WHERE chat_id=?;"); + sqlite3_bind_int (stmt, 1, chat_id); + sqlite3_step(stmt); + sqlite3_finalize(stmt); + + if( skip==NULL || strcasecmp(self_addr, skip) != 0 ) { + mrmailbox_add_contact_to_chat__(mailbox, chat_id, MR_CONTACT_ID_SELF); + } + + if( from_id > MR_CONTACT_ID_LAST_SPECIAL ) { + if( mrmailbox_contact_addr_equals__(mailbox, from_id, self_addr)==0 + && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, from_id, skip)==0) ) { + mrmailbox_add_contact_to_chat__(mailbox, chat_id, from_id); + } + } + + for( i = 0; i < to_ids_cnt; i++ ) + { + uint32_t to_id = mrarray_get_id(to_ids, i); /* to_id is only once in to_ids and is non-special */ + if( mrmailbox_contact_addr_equals__(mailbox, to_id, self_addr)==0 + && (skip==NULL || mrmailbox_contact_addr_equals__(mailbox, to_id, skip)==0) ) { + mrmailbox_add_contact_to_chat__(mailbox, chat_id, to_id); + } + } + send_EVENT_CHAT_MODIFIED = 1; + } + + if( send_EVENT_CHAT_MODIFIED ) { + mailbox->m_cb(mailbox, MR_EVENT_CHAT_MODIFIED, chat_id, 0); + } + + /* check the number of receivers - + the only critical situation is if the user hits "Reply" instead of "Reply all" in a non-messenger-client */ + if( to_ids_cnt == 1 && mime_parser->m_is_send_by_messenger==0 ) { + int is_contact_cnt = mrmailbox_get_chat_contact_count__(mailbox, chat_id); + if( is_contact_cnt > 3 /* to_ids_cnt==1 may be "From: A, To: B, SELF" as SELF is not counted in to_ids_cnt. So everything up to 3 is no error. */ ) { + chat_id = 0; + goto cleanup; + } + } + +cleanup: + free(grpid); + free(grpname); + free(self_addr); + if( ret_chat_id ) { *ret_chat_id = chat_id; } + if( ret_chat_blocked ) { *ret_chat_blocked = chat_id? chat_blocked : 0; } +} + + +/******************************************************************************* + * Receive a message and add it to the database + ******************************************************************************/ + + +void mrmailbox_receive_imf(mrmailbox_t* mailbox, const char* imf_raw_not_terminated, size_t imf_raw_bytes, + const char* server_folder, uint32_t server_uid, uint32_t flags) +{ + /* the function returns the number of created messages in the database */ + int incoming = 0; + int incoming_origin = MR_ORIGIN_UNSET; + #define outgoing (!incoming) + + mrarray_t* to_ids = NULL; + int to_self = 0; + + uint32_t from_id = 0; + int from_id_blocked = 0; + uint32_t to_id = 0; + uint32_t chat_id = 0; + int chat_id_blocked = 0; + int state = MR_STATE_UNDEFINED; + + sqlite3_stmt* stmt; + size_t i, icnt; + uint32_t first_dblocal_id = 0; + char* rfc724_mid = NULL; /* Message-ID from the header */ + time_t message_timestamp = MR_INVALID_TIMESTAMP; + mrmimeparser_t* mime_parser = mrmimeparser_new(mailbox->m_blobdir, mailbox); + int db_locked = 0; + int transaction_pending = 0; + const struct mailimf_field* field; + + carray* created_db_entries = carray_new(16); + int create_event_to_send = MR_EVENT_MSGS_CHANGED; + + carray* rr_event_to_send = carray_new(16); + + int has_return_path = 0; + char* txt_raw = NULL; + + mrmailbox_log_info(mailbox, 0, "Receive message #%lu from %s.", server_uid, server_folder? server_folder:"?"); + + to_ids = mrarray_new(mailbox, 16); + if( to_ids==NULL || created_db_entries==NULL || rr_event_to_send==NULL || mime_parser == NULL ) { + mrmailbox_log_info(mailbox, 0, "Bad param."); + goto cleanup; + } + + /* parse the imf to mailimf_message { + mailimf_fields* msg_fields { + clist* fld_list; // list of mailimf_field + } + mailimf_body* msg_body { // != NULL + const char * bd_text; // != NULL + size_t bd_size; + } + }; + normally, this is done by mailimf_message_parse(), however, as we also need the MIME data, + we use mailmime_parse() through MrMimeParser (both call mailimf_struct_multiple_parse() somewhen, I did not found out anything + that speaks against this approach yet) */ + mrmimeparser_parse(mime_parser, imf_raw_not_terminated, imf_raw_bytes); + if( mrhash_count(&mime_parser->m_header)==0 ) { + mrmailbox_log_info(mailbox, 0, "No header."); + goto cleanup; /* Error - even adding an empty record won't help as we do not know the message ID */ + } + + mrsqlite3_lock(mailbox->m_sql); + db_locked = 1; + + mrsqlite3_begin_transaction__(mailbox->m_sql); + transaction_pending = 1; + + + /* Check, if the mail comes from extern, resp. is not sent by us. This is a _really_ important step + as messages sent by us are used to validate other mail senders and receivers. + For this purpose, we assume, the `Return-Path:`-header is never present if the message is sent by us. + The `Received:`-header may be another idea, however, this is also set if mails are transfered from other accounts via IMAP. + Using `From:` alone is no good idea, as mailboxes may use different sending-addresses - moreover, they may change over the years. + However, we use `From:` as an additional hint below. */ + if( mrmimeparser_lookup_field(mime_parser, "Return-Path") ) { + has_return_path = 1; + } + + if( has_return_path ) { + incoming = 1; + } + + /* for incoming messages, get From: and check if it is known (for known From:'s we add the other To:/Cc:/Bcc: in the 3rd pass) */ + if( incoming + && (field=mrmimeparser_lookup_field(mime_parser, "From"))!=NULL + && field->fld_type==MAILIMF_FIELD_FROM) + { + struct mailimf_from* fld_from = field->fld_data.fld_from; + if( fld_from ) + { + int check_self; + mrarray_t* from_list = mrarray_new(mailbox, 16); + mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mailbox, fld_from->frm_mb_list, MR_ORIGIN_INCOMING_UNKNOWN_FROM, from_list, &check_self); + if( check_self ) + { + if( mrmimeparser_sender_equals_recipient(mime_parser) ) + { + from_id = MR_CONTACT_ID_SELF; + } + else + { + incoming = 0; /* The `Return-Path:`-approach above works well, however, there may be outgoing messages which we also receive - + for these messages, the `Return-Path:` is set although we're the sender. To correct these cases, we add an + additional From: check - which, however, will not work for older From:-addresses used on the mailbox. */ + } + } + else + { + if( mrarray_get_cnt(from_list)>=1 ) /* if there is no from given, from_id stays 0 which is just fine. These messages are very rare, however, we have to add them to the database (they go to the "deaddrop" chat) to avoid a re-download from the server. See also [**] */ + { + from_id = mrarray_get_id(from_list, 0); + incoming_origin = mrmailbox_get_contact_origin__(mailbox, from_id, &from_id_blocked); + } + } + mrarray_unref(from_list); + } + } + + /* Make sure, to_ids starts with the first To:-address (Cc: and Bcc: are added in the loop below pass) */ + if( (field=mrmimeparser_lookup_field(mime_parser, "To"))!=NULL + && field->fld_type==MAILIMF_FIELD_TO ) + { + struct mailimf_to* fld_to = field->fld_data.fld_to; /* can be NULL */ + if( fld_to ) + { + mrmailbox_add_or_lookup_contacts_by_address_list__(mailbox, fld_to->to_addr_list /*!= NULL*/, + outgoing? MR_ORIGIN_OUTGOING_TO : (incoming_origin>=MR_ORIGIN_MIN_VERIFIED? MR_ORIGIN_INCOMING_TO : MR_ORIGIN_INCOMING_UNKNOWN_TO), to_ids, &to_self); + } + } + + if( mrmimeparser_has_nonmeta(mime_parser) ) + { + + /********************************************************************** + * Add parts + *********************************************************************/ + + /* collect the rest information */ + if( (field=mrmimeparser_lookup_field(mime_parser, "Cc"))!=NULL && field->fld_type==MAILIMF_FIELD_CC ) + { + struct mailimf_cc* fld_cc = field->fld_data.fld_cc; + if( fld_cc ) { + mrmailbox_add_or_lookup_contacts_by_address_list__(mailbox, fld_cc->cc_addr_list, + outgoing? MR_ORIGIN_OUTGOING_CC : (incoming_origin>=MR_ORIGIN_MIN_VERIFIED? MR_ORIGIN_INCOMING_CC : MR_ORIGIN_INCOMING_UNKNOWN_CC), to_ids, NULL); + } + } + + if( (field=mrmimeparser_lookup_field(mime_parser, "Bcc"))!=NULL && field->fld_type==MAILIMF_FIELD_BCC ) + { + struct mailimf_bcc* fld_bcc = field->fld_data.fld_bcc; + if( outgoing && fld_bcc ) { + mrmailbox_add_or_lookup_contacts_by_address_list__(mailbox, fld_bcc->bcc_addr_list, + MR_ORIGIN_OUTGOING_BCC, to_ids, NULL); + } + } + + /* check if the message introduces a new chat: + - outgoing messages introduce a chat with the first to: address if they are sent by a messenger + - incoming messages introduce a chat only for known contacts if they are sent by a messenger + (of course, the user can add other chats manually later) */ + if( incoming ) + { + state = (flags&MR_IMAP_SEEN)? MR_STATE_IN_SEEN : MR_STATE_IN_FRESH; + to_id = MR_CONTACT_ID_SELF; + + /* test if there is a normal chat with the sender - if so, this allows us to create groups in the next step */ + uint32_t test_normal_chat_id = 0; + int test_normal_chat_id_blocked = 0; + mrmailbox_lookup_real_nchat_by_contact_id__(mailbox, from_id, &test_normal_chat_id, &test_normal_chat_id_blocked); + + /* get the chat_id - a chat_id here is no indicator that the chat is displayed in the normal list, it might also be + blocked and displayed in the deaddrop as a result */ + if( chat_id == 0 ) + { + /* try to create a group */ + int create_blocked = ((test_normal_chat_id&&test_normal_chat_id_blocked==MR_CHAT_NOT_BLOCKED) || incoming_origin>=MR_ORIGIN_MIN_START_NEW_NCHAT/*always false, for now*/)? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; + create_or_lookup_group__(mailbox, mime_parser, create_blocked, from_id, to_ids, &chat_id, &chat_id_blocked); + if( chat_id && chat_id_blocked && !create_blocked ) { + mrmailbox_unblock_chat__(mailbox, chat_id); + chat_id_blocked = 0; + } + } + + if( chat_id == 0 ) + { + /* check if the message belongs to a mailing list */ + if( mrmimeparser_is_mailinglist_message(mime_parser) ) { + chat_id = MR_CHAT_ID_TRASH; + mrmailbox_log_info(mailbox, 0, "Message belongs to a mailing list and is ignored."); + } + } + + if( chat_id == 0 ) + { + /* try to create a normal chat */ + int create_blocked = (incoming_origin>=MR_ORIGIN_MIN_START_NEW_NCHAT/*always false, for now*/ || from_id==to_id)? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; + if( test_normal_chat_id ) { + chat_id = test_normal_chat_id; + chat_id_blocked = test_normal_chat_id_blocked; + } + else { + mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, from_id, create_blocked, &chat_id, &chat_id_blocked); + } + + if( chat_id && chat_id_blocked ) { + if( !create_blocked ) { + mrmailbox_unblock_chat__(mailbox, chat_id); + chat_id_blocked = 0; + } + else if( mrmailbox_is_reply_to_known_message__(mailbox, mime_parser) ) { + mrmailbox_scaleup_contact_origin__(mailbox, from_id, MR_ORIGIN_INCOMING_REPLY_TO); /* we do not want any chat to be created implicitly. Because of the origin-scale-up, the contact requests will pop up and this should be just fine. */ + mrmailbox_log_info(mailbox, 0, "Message is a reply to a known message, mark sender as known."); + incoming_origin = MR_MAX(incoming_origin, MR_ORIGIN_INCOMING_REPLY_TO); + } + } + } + + if( chat_id == 0 ) + { + /* maybe from_id is null or sth. else is suspicious, move message to trash */ + chat_id = MR_CHAT_ID_TRASH; + } + + /* degrade state for unknown senders and non-delta messages + (the latter may be removed if we run into spam problems, currently this is fine) + (noticed messages do count as being unread; therefore, the deaddrop will not popup in the chatlist) */ + if( chat_id_blocked && state == MR_STATE_IN_FRESH ) { + if( incoming_originm_is_send_by_messenger==0 ) { + state = MR_STATE_IN_NOTICED; + } + } + } + else /* outgoing */ + { + state = MR_STATE_OUT_DELIVERED; /* the mail is on the IMAP server, probably it is also delivered. We cannot recreate other states (read, error). */ + from_id = MR_CONTACT_ID_SELF; + if( mrarray_get_cnt(to_ids) >= 1 ) { + to_id = mrarray_get_id(to_ids, 0); + + if( chat_id == 0 ) + { + create_or_lookup_group__(mailbox, mime_parser, MR_CHAT_NOT_BLOCKED, from_id, to_ids, &chat_id, &chat_id_blocked); + if( chat_id && chat_id_blocked ) { + mrmailbox_unblock_chat__(mailbox, chat_id); + chat_id_blocked = 0; + } + } + + if( chat_id == 0 ) + { + int create_blocked = (mime_parser->m_is_send_by_messenger && !mrmailbox_is_contact_blocked__(mailbox, to_id))? MR_CHAT_NOT_BLOCKED : MR_CHAT_DEADDROP_BLOCKED; + mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, to_id, create_blocked, &chat_id, &chat_id_blocked); + if( chat_id && chat_id_blocked && !create_blocked ) { + mrmailbox_unblock_chat__(mailbox, chat_id); + chat_id_blocked = 0; + } + } + } + + if( chat_id == 0 ) { + if( mrarray_get_cnt(to_ids) == 0 && to_self ) { + /* from_id == to_id == MR_CONTACT_ID_SELF - this is a self-sent messages, maybe an Autocrypt Setup Message */ + mrmailbox_create_or_lookup_nchat_by_contact_id__(mailbox, MR_CONTACT_ID_SELF, MR_CHAT_NOT_BLOCKED, &chat_id, &chat_id_blocked); + if( chat_id && chat_id_blocked ) { + mrmailbox_unblock_chat__(mailbox, chat_id); + chat_id_blocked = 0; + } + } + } + + if( chat_id == 0 ) { + chat_id = MR_CHAT_ID_TRASH; + } + } + + /* correct message_timestamp, it should not be used before, + however, we cannot do this earlier as we need from_id to be set */ + if( (field=mrmimeparser_lookup_field(mime_parser, "Date"))!=NULL && field->fld_type==MAILIMF_FIELD_ORIG_DATE ) { + struct mailimf_orig_date* orig_date = field->fld_data.fld_orig_date; + if( orig_date ) { + message_timestamp = mr_timestamp_from_date(orig_date->dt_date_time); /* is not yet checked against bad times! */ + } + } + message_timestamp = mrmailbox_correct_bad_timestamp__(mailbox, chat_id, from_id, message_timestamp, (flags&MR_IMAP_SEEN)? 0 : 1 /*fresh message?*/); + + /* unarchive chat */ + mrmailbox_unarchive_chat__(mailbox, chat_id); + + /* check, if the mail is already in our database - if so, there's nothing more to do + (we may get a mail twice eg. if it is moved between folders) */ + if( (field=mrmimeparser_lookup_field(mime_parser, "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 ) { + rfc724_mid = safe_strdup(fld_message_id->mid_value); + } + } + + if( rfc724_mid == NULL ) { + /* header is lacking a Message-ID - this may be the case, if the message was sent from this account and the mail client + the the SMTP-server set the ID (true eg. for the Webmailer used in all-inkl-KAS) + in these cases, we build a message ID based on some useful header fields that do never change (date, to) + we do not use the folder-local id, as this will change if the mail is moved to another folder. */ + rfc724_mid = mr_create_incoming_rfc724_mid(message_timestamp, from_id, to_ids); + if( rfc724_mid == NULL ) { + mrmailbox_log_info(mailbox, 0, "Cannot create Message-ID."); + goto cleanup; + } + } + + { + char* old_server_folder = NULL; + uint32_t old_server_uid = 0; + if( mrmailbox_rfc724_mid_exists__(mailbox, rfc724_mid, &old_server_folder, &old_server_uid) ) { + /* The message is already added to our database; rollback. If needed, update the server_uid which may have changed if the message was moved around on the server. */ + if( strcmp(old_server_folder, server_folder)!=0 || old_server_uid!=server_uid ) { + mrsqlite3_rollback__(mailbox->m_sql); + transaction_pending = 0; + mrmailbox_update_server_uid__(mailbox, rfc724_mid, server_folder, server_uid); + } + free(old_server_folder); + mrmailbox_log_info(mailbox, 0, "Message already in DB."); + goto cleanup; + } + } + + /* if the message is not sent by a messenger, check if it is sent at least as a reply to a messenger message + (later, we move these replies to the Chats-folder) */ + int msgrmsg = mime_parser->m_is_send_by_messenger; /* 1 or 0 for yes/no */ + if( msgrmsg ) + { + mrmailbox_log_info(mailbox, 0, "Message sent by another messenger (will be moved to Chats-folder)."); + } + else + { + if( mrmailbox_is_reply_to_messenger_message__(mailbox, mime_parser) ) + { + mrmailbox_log_info(mailbox, 0, "Message is a reply to a messenger message (will be moved to Chats-folder)."); + msgrmsg = 2; /* 2=no, but is reply to messenger message */ + } + } + + /* 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)) */ + icnt = carray_count(mime_parser->m_parts); /* should be at least one - maybe empty - part */ + for( i = 0; i < icnt; i++ ) + { + mrmimepart_t* part = (mrmimepart_t*)carray_get(mime_parser->m_parts, i); + if( part->m_is_meta ) { + continue; + } + + if( part->m_type == MR_MSG_TEXT ) { + txt_raw = mr_mprintf("%s\n\n%s", mime_parser->m_subject? mime_parser->m_subject : "", part->m_msg_raw); + } + + if( mime_parser->m_is_system_message ) { + mrparam_set_int(part->m_param, MRP_SYSTEM_CMD, mime_parser->m_is_system_message); + } + + stmt = mrsqlite3_predefine__(mailbox->m_sql, INSERT_INTO_msgs_msscftttsmttpb, + "INSERT INTO msgs (rfc724_mid,server_folder,server_uid,chat_id,from_id, to_id,timestamp,type, state,msgrmsg,txt,txt_raw,param,bytes)" + " VALUES (?,?,?,?,?, ?,?,?, ?,?,?,?,?,?);"); + sqlite3_bind_text (stmt, 1, rfc724_mid, -1, SQLITE_STATIC); + sqlite3_bind_text (stmt, 2, server_folder, -1, SQLITE_STATIC); + sqlite3_bind_int (stmt, 3, server_uid); + sqlite3_bind_int (stmt, 4, chat_id); + sqlite3_bind_int (stmt, 5, from_id); + sqlite3_bind_int (stmt, 6, to_id); + sqlite3_bind_int64(stmt, 7, message_timestamp); + sqlite3_bind_int (stmt, 8, part->m_type); + sqlite3_bind_int (stmt, 9, state); + sqlite3_bind_int (stmt, 10, msgrmsg); + sqlite3_bind_text (stmt, 11, part->m_msg? part->m_msg : "", -1, SQLITE_STATIC); + sqlite3_bind_text (stmt, 12, txt_raw? txt_raw : "", -1, SQLITE_STATIC); + sqlite3_bind_text (stmt, 13, part->m_param->m_packed, -1, SQLITE_STATIC); + sqlite3_bind_int (stmt, 14, part->m_bytes); + if( sqlite3_step(stmt) != SQLITE_DONE ) { + mrmailbox_log_info(mailbox, 0, "Cannot write DB."); + goto cleanup; /* i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record */ + } + + free(txt_raw); + txt_raw = NULL; + + if( first_dblocal_id == 0 ) { + first_dblocal_id = sqlite3_last_insert_rowid(mailbox->m_sql->m_cobj); + } + + carray_add(created_db_entries, (void*)(uintptr_t)chat_id, NULL); + carray_add(created_db_entries, (void*)(uintptr_t)first_dblocal_id, NULL); + } + + mrmailbox_log_info(mailbox, 0, "Message has %i parts and is moved to chat #%i.", icnt, chat_id); + + /* check event to send */ + if( chat_id == MR_CHAT_ID_TRASH ) + { + create_event_to_send = 0; + } + else if( incoming && state==MR_STATE_IN_FRESH ) + { + if( from_id_blocked ) { + create_event_to_send = 0; + } + else if( chat_id_blocked ) { + create_event_to_send = MR_EVENT_MSGS_CHANGED; + /*if( mrsqlite3_get_config_int__(mailbox->m_sql, "show_deaddrop", 0)!=0 ) { + create_event_to_send = MR_EVENT_INCOMING_MSG; + }*/ + } + else { + create_event_to_send = MR_EVENT_INCOMING_MSG; + } + } + } + + + if( carray_count(mime_parser->m_reports) > 0 ) + { + /****************************************************************** + * Handle reports (mainly MDNs) + *****************************************************************/ + + int mdns_enabled = mrsqlite3_get_config_int__(mailbox->m_sql, "mdns_enabled", MR_MDNS_DEFAULT_ENABLED); + icnt = carray_count(mime_parser->m_reports); + for( i = 0; i < icnt; i++ ) + { + int mdn_consumed = 0; + struct mailmime* report_root = carray_get(mime_parser->m_reports, i); + struct mailmime_parameter* report_type = mailmime_find_ct_parameter(report_root, "report-type"); + if( report_root==NULL || report_type==NULL || report_type->pa_value==NULL ) { + continue; + } + + if( strcmp(report_type->pa_value, "disposition-notification") == 0 + && clist_count(report_root->mm_data.mm_multipart.mm_mp_list) >= 2 /* the first part is for humans, the second for machines */ ) + { + if( mdns_enabled /*to get a clear functionality, do not show incoming MDNs if the options is disabled*/ ) + { + struct mailmime* report_data = (struct mailmime*)clist_content(clist_next(clist_begin(report_root->mm_data.mm_multipart.mm_mp_list))); + if( report_data + && report_data->mm_content_type->ct_type->tp_type==MAILMIME_TYPE_COMPOSITE_TYPE + && report_data->mm_content_type->ct_type->tp_data.tp_composite_type->ct_type==MAILMIME_COMPOSITE_TYPE_MESSAGE + && strcmp(report_data->mm_content_type->ct_subtype, "disposition-notification")==0 ) + { + /* we received a MDN (although the MDN is only a header, we parse it as a complete mail) */ + const char* report_body = NULL; + size_t report_body_bytes = 0; + char* to_mmap_string_unref = NULL; + if( mailmime_transfer_decode(report_data, &report_body, &report_body_bytes, &to_mmap_string_unref) ) + { + struct mailmime* report_parsed = NULL; + size_t dummy = 0; + if( mailmime_parse(report_body, report_body_bytes, &dummy, &report_parsed)==MAIL_NO_ERROR + && report_parsed!=NULL ) + { + struct mailimf_fields* report_fields = mailmime_find_mailimf_fields(report_parsed); + if( report_fields ) + { + struct mailimf_optional_field* of_disposition = mailimf_find_optional_field(report_fields, "Disposition"); /* MUST be preset, _if_ preset, we assume a sort of attribution and do not go into details */ + struct mailimf_optional_field* of_org_msgid = mailimf_find_optional_field(report_fields, "Original-Message-ID"); /* can't live without */ + if( of_disposition && of_disposition->fld_value && of_org_msgid && of_org_msgid->fld_value ) + { + char* rfc724_mid = NULL; + dummy = 0; + if( mailimf_msg_id_parse(of_org_msgid->fld_value, strlen(of_org_msgid->fld_value), &dummy, &rfc724_mid)==MAIL_NO_ERROR + && rfc724_mid!=NULL ) + { + uint32_t chat_id = 0; + uint32_t msg_id = 0; + if( mrmailbox_mdn_from_ext__(mailbox, from_id, rfc724_mid, &chat_id, &msg_id) ) { + carray_add(rr_event_to_send, (void*)(uintptr_t)chat_id, NULL); + carray_add(rr_event_to_send, (void*)(uintptr_t)msg_id, NULL); + } + mdn_consumed = (msg_id!=0); + free(rfc724_mid); + } + } + } + mailmime_free(report_parsed); + } + + if( to_mmap_string_unref ) { mmap_string_unref(to_mmap_string_unref); } + } + } + } + + /* Move the MDN away to the chats folder. We do this for: + - Consumed or not consumed MDNs from other messengers + - Consumed MDNs from normal MUAs + Unconsumed MDNs from normal MUAs are _not_ moved. + NB: we do not delete the MDN as it may be used by other clients + + CAVE: we rely on mrimap_markseen_msg() not to move messages that are already in the correct folder. + otherwise, the moved message get a new server_uid and is "fresh" again and we will be here again to move it away - + 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); + free(jobparam); + } + } + + } /* for() */ + + } + + /* debug print? */ + if( mrsqlite3_get_config_int__(mailbox->m_sql, "save_eml", 0) ) { + char* emlname = mr_mprintf("%s/%s-%i.eml", mailbox->m_blobdir, server_folder, (int)first_dblocal_id /*may be 0 for MDNs*/); + FILE* emlfileob = fopen(emlname, "w"); + if( emlfileob ) { + fwrite(imf_raw_not_terminated, 1, imf_raw_bytes, emlfileob); + fclose(emlfileob); + } + free(emlname); + } + + /* end sql-transaction */ + mrsqlite3_commit__(mailbox->m_sql); + transaction_pending = 0; + + /* done */ +cleanup: + if( transaction_pending ) { + mrsqlite3_rollback__(mailbox->m_sql); + } + + if( db_locked ) { + mrsqlite3_unlock(mailbox->m_sql); + } + + if( mime_parser ) { + mrmimeparser_unref(mime_parser); + } + + if( rfc724_mid ) { + free(rfc724_mid); + } + + if( to_ids ) { + mrarray_unref(to_ids); + } + + if( created_db_entries ) { + if( create_event_to_send ) { + size_t i, icnt = carray_count(created_db_entries); + for( i = 0; i < icnt; i += 2 ) { + mailbox->m_cb(mailbox, create_event_to_send, (uintptr_t)carray_get(created_db_entries, i), (uintptr_t)carray_get(created_db_entries, i+1)); + } + } + carray_free(created_db_entries); + } + + if( rr_event_to_send ) { + size_t i, icnt = carray_count(rr_event_to_send); + for( i = 0; i < icnt; i += 2 ) { + mailbox->m_cb(mailbox, MR_EVENT_MSG_READ, (uintptr_t)carray_get(rr_event_to_send, i), (uintptr_t)carray_get(rr_event_to_send, i+1)); + } + carray_free(rr_event_to_send); + } + + free(txt_raw); +} diff --git a/src/mrmailbox_tools.c b/src/mrmailbox_tools.c deleted file mode 100644 index fb7119a2..00000000 --- a/src/mrmailbox_tools.c +++ /dev/null @@ -1,292 +0,0 @@ -/******************************************************************************* - * - * Delta Chat Core - * Copyright (C) 2017 Björn Petersen - * Contact: r10s@b44t.com, http://b44t.com - * - * This program is free software: you can redistribute it and/or modify it under - * the terms of the GNU General Public License as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) any later - * version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see http://www.gnu.org/licenses/ . - * - ******************************************************************************/ - - -#include "mrmailbox_internal.h" -#include "mrmimeparser.h" - - -/******************************************************************************* - * Add contacts to database on receiving messages - ******************************************************************************/ - - -static void add_or_lookup_contact_by_addr__(mrmailbox_t* ths, const char* display_name_enc, const char* addr_spec, int origin, mrarray_t* ids, int* check_self) -{ - /* is addr_spec equal to SELF? */ - int dummy; - if( check_self == NULL ) { check_self = &dummy; } - - *check_self = 0; - - char* self_addr = mrsqlite3_get_config__(ths->m_sql, "configured_addr", ""); - if( strcasecmp(self_addr, addr_spec)==0 ) { - *check_self = 1; - } - free(self_addr); - - if( *check_self ) { - return; - } - - /* add addr_spec if missing, update otherwise */ - char* display_name_dec = NULL; - if( display_name_enc ) { - display_name_dec = mr_decode_header_string(display_name_enc); - mr_normalize_name(display_name_dec); - } - - uint32_t row_id = mrmailbox_add_or_lookup_contact__(ths, display_name_dec /*can be NULL*/, addr_spec, origin, NULL); - - free(display_name_dec); - - if( row_id ) { - if( !mrarray_search_id(ids, row_id, NULL) ) { - mrarray_add_id(ids, row_id); - } - } -} - - -void mrmailbox_add_or_lookup_contacts_by_mailbox_list__(mrmailbox_t* ths, struct mailimf_mailbox_list* mb_list, int origin, mrarray_t* ids, int* check_self) -{ - clistiter* cur; - for( cur = clist_begin(mb_list->mb_list); cur!=NULL ; cur=clist_next(cur) ) { - struct mailimf_mailbox* mb = (struct mailimf_mailbox*)clist_content(cur); - if( mb ) { - add_or_lookup_contact_by_addr__(ths, mb->mb_display_name, mb->mb_addr_spec, origin, ids, check_self); - } - } -} - - -void mrmailbox_add_or_lookup_contacts_by_address_list__(mrmailbox_t* ths, struct mailimf_address_list* adr_list, int origin, mrarray_t* ids, int* check_self) -{ - clistiter* cur; - for( cur = clist_begin(adr_list->ad_list); cur!=NULL ; cur=clist_next(cur) ) { - struct mailimf_address* adr = (struct mailimf_address*)clist_content(cur); - if( adr ) { - if( adr->ad_type == MAILIMF_ADDRESS_MAILBOX ) { - struct mailimf_mailbox* mb = adr->ad_data.ad_mailbox; /* can be NULL */ - if( mb ) { - add_or_lookup_contact_by_addr__(ths, mb->mb_display_name, mb->mb_addr_spec, origin, ids, check_self); - } - } - else if( adr->ad_type == MAILIMF_ADDRESS_GROUP ) { - struct mailimf_group* group = adr->ad_data.ad_group; /* can be NULL */ - if( group && group->grp_mb_list /*can be NULL*/ ) { - mrmailbox_add_or_lookup_contacts_by_mailbox_list__(ths, group->grp_mb_list, origin, ids, check_self); - } - } - } - } -} - - -/******************************************************************************* - * Check if a message is a reply to a known message (messenger or non-messenger) - ******************************************************************************/ - - -static int is_known_rfc724_mid__(mrmailbox_t* mailbox, const char* rfc724_mid) -{ - if( rfc724_mid ) { - sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_msgs_WHERE_cm, - "SELECT m.id FROM msgs m " - " LEFT JOIN chats c ON m.chat_id=c.id " - " WHERE m.rfc724_mid=? " - " AND m.chat_id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) - " AND c.blocked=0;"); - sqlite3_bind_text(stmt, 1, rfc724_mid, -1, SQLITE_STATIC); - if( sqlite3_step(stmt) == SQLITE_ROW ) { - return 1; - } - } - return 0; -} - - -static int is_known_rfc724_mid_in_list__(mrmailbox_t* mailbox, const clist* mid_list) -{ - if( mid_list ) { - clistiter* cur; - for( cur = clist_begin(mid_list); cur!=NULL ; cur=clist_next(cur) ) { - if( is_known_rfc724_mid__(mailbox, clist_content(cur)) ) { - return 1; - } - } - } - - return 0; -} - - -int mrmailbox_is_reply_to_known_message__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser) -{ - /* check if the message is a reply to a known message; the replies are identified by the Message-ID from - `In-Reply-To`/`References:` (to support non-Delta-Clients) or from `Chat-Predecessor:` (Delta clients, see comment in mrchat.c) */ - - struct mailimf_optional_field* optional_field; - if( (optional_field=mrmimeparser_lookup_optional_field2(mime_parser, "Chat-Predecessor", "X-MrPredecessor")) != NULL ) - { - if( is_known_rfc724_mid__(mailbox, optional_field->fld_value) ) { - return 1; - } - } - - struct mailimf_field* field; - if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL - && field->fld_type == MAILIMF_FIELD_IN_REPLY_TO ) - { - struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; - if( fld_in_reply_to ) { - if( is_known_rfc724_mid_in_list__(mailbox, field->fld_data.fld_in_reply_to->mid_list) ) { - return 1; - } - } - } - - if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL - && field->fld_type == MAILIMF_FIELD_REFERENCES ) - { - struct mailimf_references* fld_references = field->fld_data.fld_references; - if( fld_references ) { - if( is_known_rfc724_mid_in_list__(mailbox, field->fld_data.fld_references->mid_list) ) { - return 1; - } - } - } - - return 0; -} - - -/******************************************************************************* - * Check if a message is a reply to any messenger message - ******************************************************************************/ - - -static int is_msgrmsg_rfc724_mid__(mrmailbox_t* mailbox, const char* rfc724_mid) -{ - if( rfc724_mid ) { - sqlite3_stmt* stmt = mrsqlite3_predefine__(mailbox->m_sql, SELECT_id_FROM_msgs_WHERE_mcm, - "SELECT id FROM msgs " - " WHERE rfc724_mid=? " - " AND msgrmsg!=0 " - " AND chat_id>" MR_STRINGIFY(MR_CHAT_ID_LAST_SPECIAL) ";"); - sqlite3_bind_text(stmt, 1, rfc724_mid, -1, SQLITE_STATIC); - if( sqlite3_step(stmt) == SQLITE_ROW ) { - return 1; - } - } - return 0; -} - - -static int is_msgrmsg_rfc724_mid_in_list__(mrmailbox_t* mailbox, const clist* mid_list) -{ - if( mid_list ) { - clistiter* cur; - for( cur = clist_begin(mid_list); cur!=NULL ; cur=clist_next(cur) ) { - if( is_msgrmsg_rfc724_mid__(mailbox, clist_content(cur)) ) { - return 1; - } - } - } - - return 0; -} - - -int mrmailbox_is_reply_to_messenger_message__(mrmailbox_t* mailbox, mrmimeparser_t* mime_parser) -{ - /* function checks, if the message defined by mime_parser references a message send by us from Delta Chat. - - This is similar to is_reply_to_known_message__() but - - checks also if any of the referenced IDs are send by a messenger - - it is okay, if the referenced messages are moved to trash here - - no check for the Chat-* headers (function is only called if it is no messenger message itself) */ - - struct mailimf_field* field; - if( (field=mrmimeparser_lookup_field(mime_parser, "In-Reply-To"))!=NULL - && field->fld_type==MAILIMF_FIELD_IN_REPLY_TO ) - { - struct mailimf_in_reply_to* fld_in_reply_to = field->fld_data.fld_in_reply_to; - if( fld_in_reply_to ) { - if( is_msgrmsg_rfc724_mid_in_list__(mailbox, field->fld_data.fld_in_reply_to->mid_list) ) { - return 1; - } - } - } - - if( (field=mrmimeparser_lookup_field(mime_parser, "References"))!=NULL - && field->fld_type==MAILIMF_FIELD_REFERENCES ) - { - struct mailimf_references* fld_references = field->fld_data.fld_references; - if( fld_references ) { - if( is_msgrmsg_rfc724_mid_in_list__(mailbox, field->fld_data.fld_references->mid_list) ) { - return 1; - } - } - } - - return 0; -} - - -/******************************************************************************* - * Misc. - ******************************************************************************/ - - -time_t mrmailbox_correct_bad_timestamp__(mrmailbox_t* ths, uint32_t chat_id, uint32_t from_id, time_t desired_timestamp, int is_fresh_msg) -{ - /* used for correcting timestamps of _received_ messages. - use the last message from another user (including SELF) as the MINIMUM - (we do this check only for fresh messages, other messages may pop up whereever, this may happen eg. when restoring old messages or synchronizing different clients) */ - if( is_fresh_msg ) - { - sqlite3_stmt* stmt = mrsqlite3_predefine__(ths->m_sql, SELECT_timestamp_FROM_msgs_WHERE_timestamp, - "SELECT MAX(timestamp) FROM msgs WHERE chat_id=? and from_id!=? AND timestamp>=?"); - sqlite3_bind_int (stmt, 1, chat_id); - sqlite3_bind_int (stmt, 2, from_id); - sqlite3_bind_int64(stmt, 3, desired_timestamp); - if( sqlite3_step(stmt)==SQLITE_ROW ) - { - time_t last_msg_time = sqlite3_column_int64(stmt, 0); - if( last_msg_time > 0 /* may happen as we do not check against sqlite3_column_type()!=SQLITE_NULL */ ) { - if( desired_timestamp <= last_msg_time ) { - desired_timestamp = last_msg_time+1; /* this may result in several incoming messages having the same - one-second-after-the-last-other-message-timestamp. however, this is no big deal - as we do not try to recrete the order of bad-date-messages and as we always order by ID as second criterion */ - } - } - } - } - - /* use the (smeared) current time as the MAXIMUM */ - if( desired_timestamp >= mr_smeared_time__() ) - { - desired_timestamp = mr_create_smeared_timestamp__(); - } - - return desired_timestamp; -} \ No newline at end of file