diff --git a/src/dc_mimefactory.rs b/src/dc_mimefactory.rs index 9523b78cb..479fd4224 100644 --- a/src/dc_mimefactory.rs +++ b/src/dc_mimefactory.rs @@ -26,6 +26,8 @@ use crate::location; use crate::message::{self, Message}; use crate::param::*; use crate::stock::StockMessage; +use crate::wrapmime; +use crate::wrapmime::*; #[derive(Clone, Copy, Eq, PartialEq)] pub enum Loaded { @@ -319,13 +321,13 @@ pub unsafe fn dc_mimefactory_render( /* Add a X-Mailer header. This is only informational for debugging and may be removed in the release. We do not rely on this header as it may be removed by MTAs. */ - add_mailimf_field(imf_fields, "X-Mailer", &headerval); - add_mailimf_field(imf_fields, "Chat-Version", "1.0"); + new_custom_field(imf_fields, "X-Mailer", &headerval); + new_custom_field(imf_fields, "Chat-Version", "1.0"); if factory.req_mdn { /* we use "Chat-Disposition-Notification-To" because replies to "Disposition-Notification-To" are weird in many cases eg. are just freetext and/or do not follow any standard. */ - add_mailimf_field( + new_custom_field( imf_fields, "Chat-Disposition-Notification-To", &factory.from_addr, @@ -355,7 +357,7 @@ pub unsafe fn dc_mimefactory_render( let mut placeholdertext = None; if chat.typ == Chattype::VerifiedGroup { - add_mailimf_field(imf_fields, "Chat-Verified", "1"); + new_custom_field(imf_fields, "Chat-Verified", "1"); force_plaintext = 0; e2ee_guaranteed = true; min_verified = 2 @@ -385,16 +387,16 @@ pub unsafe fn dc_mimefactory_render( /* build header etc. */ let command = factory.msg.param.get_cmd(); if chat.typ == Chattype::Group || chat.typ == Chattype::VerifiedGroup { - add_mailimf_field(imf_fields, "Chat-Group-ID", &chat.grpid); + new_custom_field(imf_fields, "Chat-Group-ID", &chat.grpid); let encoded = dc_encode_header_words(&chat.name); - add_mailimf_field(imf_fields, "Chat-Group-Name", &encoded); + new_custom_field(imf_fields, "Chat-Group-Name", &encoded); match command { SystemMessage::MemberRemovedFromGroup => { let email_to_remove = factory.msg.param.get(Param::Arg).unwrap_or_default(); if !email_to_remove.is_empty() { - add_mailimf_field( + new_custom_field( imf_fields, "Chat-Group-Member-Removed", &email_to_remove, @@ -406,7 +408,7 @@ pub unsafe fn dc_mimefactory_render( do_gossip = true; let email_to_add = msg.param.get(Param::Arg).unwrap_or_default(); if !email_to_add.is_empty() { - add_mailimf_field(imf_fields, "Chat-Group-Member-Added", &email_to_add); + new_custom_field(imf_fields, "Chat-Group-Member-Added", &email_to_add); grpimage = chat.param.get(Param::ProfileImage); } if 0 != msg.param.get_int(Param::Arg2).unwrap_or_default() & 0x1 { @@ -415,20 +417,20 @@ pub unsafe fn dc_mimefactory_render( "sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>", "vg-member-added", ); - add_mailimf_field(imf_fields, "Secure-Join", "vg-member-added"); + new_custom_field(imf_fields, "Secure-Join", "vg-member-added"); } } SystemMessage::GroupNameChanged => { let msg = &factory.msg; let value_to_add = msg.param.get(Param::Arg).unwrap_or_default(); - add_mailimf_field(imf_fields, "Chat-Group-Name-Changed", &value_to_add); + new_custom_field(imf_fields, "Chat-Group-Name-Changed", &value_to_add); } SystemMessage::GroupImageChanged => { let msg = &factory.msg; grpimage = msg.param.get(Param::Arg); if grpimage.is_none() { - add_mailimf_field(imf_fields, "Chat-Group-Image", "0"); + new_custom_field(imf_fields, "Chat-Group-Image", "0"); } } _ => {} @@ -437,10 +439,10 @@ pub unsafe fn dc_mimefactory_render( match command { SystemMessage::LocationStreamingEnabled => { - add_mailimf_field(imf_fields, "Chat-Content", "location-streaming-enabled"); + new_custom_field(imf_fields, "Chat-Content", "location-streaming-enabled"); } SystemMessage::AutocryptSetupMessage => { - add_mailimf_field(imf_fields, "Autocrypt-Setup-Message", "v1"); + new_custom_field(imf_fields, "Autocrypt-Setup-Message", "v1"); placeholdertext = Some( factory .context @@ -456,10 +458,10 @@ pub unsafe fn dc_mimefactory_render( context, "sending secure-join message \'{}\' >>>>>>>>>>>>>>>>>>>>>>>>>", step, ); - add_mailimf_field(imf_fields, "Secure-Join", &step); + new_custom_field(imf_fields, "Secure-Join", &step); let param2 = msg.param.get(Param::Arg2).unwrap_or_default(); if !param2.is_empty() { - add_mailimf_field( + new_custom_field( imf_fields, if step == "vg-request-with-auth" || step == "vc-request-with-auth" { @@ -472,11 +474,11 @@ pub unsafe fn dc_mimefactory_render( } let fingerprint = msg.param.get(Param::Arg3).unwrap_or_default(); if !fingerprint.is_empty() { - add_mailimf_field(imf_fields, "Secure-Join-Fingerprint", &fingerprint); + new_custom_field(imf_fields, "Secure-Join-Fingerprint", &fingerprint); } match msg.param.get(Param::Arg4) { Some(id) => { - add_mailimf_field(imf_fields, "Secure-Join-Group", &id); + new_custom_field(imf_fields, "Secure-Join-Group", &id); } None => {} }; @@ -495,7 +497,7 @@ pub unsafe fn dc_mimefactory_render( meta_part = res.0; let filename_as_sent = res.1; if !meta_part.is_null() { - add_mailimf_field(imf_fields, "Chat-Group-Image", &filename_as_sent) + new_custom_field(imf_fields, "Chat-Group-Image", &filename_as_sent) } } @@ -504,7 +506,7 @@ pub unsafe fn dc_mimefactory_render( || factory.msg.type_0 == Viewtype::Video { if factory.msg.type_0 == Viewtype::Voice { - add_mailimf_field(imf_fields, "Chat-Voice-Message", "1"); + new_custom_field(imf_fields, "Chat-Voice-Message", "1"); } let duration_ms = factory .msg @@ -513,7 +515,7 @@ pub unsafe fn dc_mimefactory_render( .unwrap_or_default(); if duration_ms > 0 { let dur = duration_ms.to_string(); - add_mailimf_field(imf_fields, "Chat-Duration", &dur); + new_custom_field(imf_fields, "Chat-Duration", &dur); } } @@ -554,7 +556,7 @@ pub unsafe fn dc_mimefactory_render( if !footer.is_empty() { "-- \r\n" } else { "" }, footer ); - let text_part = build_body_text(&message_text); + let text_part = build_body_text(&message_text)?; mailmime_smart_add_part(message, text_part); /* add attachment part */ @@ -582,35 +584,24 @@ pub unsafe fn dc_mimefactory_render( param.get_float(Param::SetLatitude).unwrap_or_default(), param.get_float(Param::SetLongitude).unwrap_or_default(), ); - let content_type = mailmime_content_new_with_str( - b"application/vnd.google-earth.kml+xml\x00" as *const u8 as *const libc::c_char, - ); - let mime_fields = mailmime_fields_new_filename( - MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int, - "message.kml".strdup(), - MAILMIME_MECHANISM_8BIT as libc::c_int, - ); - let kml_mime_part = mailmime_new_empty(content_type, mime_fields); - set_body_text(kml_mime_part, &kml_file); - mailmime_smart_add_part(message, kml_mime_part); + add_filename_part( + message, + "message.kml", + "application/vnd.google-earth.kml+xml", + &kml_file, + )?; } if location::is_sending_locations_to_chat(context, factory.msg.chat_id) { if let Ok((kml_file, last_added_location_id)) = location::get_kml(context, factory.msg.chat_id) { - let content_type = mailmime_content_new_with_str( - b"application/vnd.google-earth.kml+xml\x00" as *const u8 - as *const libc::c_char, - ); - let mime_fields = mailmime_fields_new_filename( - MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int, - "location.kml".strdup(), - MAILMIME_MECHANISM_8BIT as libc::c_int, - ); - let kml_mime_part = mailmime_new_empty(content_type, mime_fields); - set_body_text(kml_mime_part, &kml_file); - mailmime_smart_add_part(message, kml_mime_part); + add_filename_part( + message, + "location.kml", + "application/vnd.google-earth.kml+xml", + &kml_file, + )?; if !factory.msg.param.exists(Param::SetLatitude) { // otherwise, the independent location is already filed factory.out_last_added_location_id = last_added_location_id; @@ -624,18 +615,14 @@ pub unsafe fn dc_mimefactory_render( *********************************************************************/ /* RFC 6522, this also requires the `report-type` parameter which is equal to the MIME subtype of the second body part of the multipart/report */ - let multipart: *mut mailmime = + let multipart = mailmime_multiple_new(b"multipart/report\x00" as *const u8 as *const libc::c_char); - let content: *mut mailmime_content = (*multipart).mm_content_type; - clist_insert_after( - (*content).ct_parameters, - (*(*content).ct_parameters).last, - mailmime_param_new_with_data( - b"report-type\x00" as *const u8 as *const libc::c_char as *mut libc::c_char, - b"disposition-notification\x00" as *const u8 as *const libc::c_char - as *mut libc::c_char, - ) as *mut libc::c_void, - ); + wrapmime::append_ct_param( + (*multipart).mm_content_type, + "report-type", + "disposition-notification", + )?; + mailmime_add_part(message, multipart); /* first body part: always human-readable, always REQUIRED by RFC 6522 */ @@ -657,7 +644,7 @@ pub unsafe fn dc_mimefactory_render( .context .stock_string_repl_str(StockMessage::ReadRcptMailBody, p1); let message_text = format!("{}\r\n", p2); - let human_mime_part: *mut mailmime = build_body_text(&message_text); + let human_mime_part = build_body_text(&message_text)?; mailmime_add_part(multipart, human_mime_part); /* second body part: machine-readable, always REQUIRED by RFC 6522 */ @@ -670,13 +657,11 @@ pub unsafe fn dc_mimefactory_render( factory.msg.rfc724_mid ); - let content_type_0: *mut mailmime_content = mailmime_content_new_with_str( - b"message/disposition-notification\x00" as *const u8 as *const libc::c_char, - ); + let content_type_0 = new_mailmime_content_type("message/disposition-notification"); let mime_fields_0: *mut mailmime_fields = mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int); let mach_mime_part: *mut mailmime = mailmime_new_empty(content_type_0, mime_fields_0); - set_body_text(mach_mime_part, &message_text2); + set_body_text(mach_mime_part, &message_text2)?; mailmime_add_part(multipart, mach_mime_part); force_plaintext = DC_FP_NO_AUTOCRYPT_HEADER; /* currently, we do not send MDNs encrypted: @@ -730,7 +715,7 @@ pub unsafe fn dc_mimefactory_render( let mut e2ee_helper = E2eeHelper::default(); if force_plaintext != DC_FP_NO_AUTOCRYPT_HEADER { - e2ee_helper.encrypt( + if e2ee_helper.encrypt( factory.context, &factory.recipients_addr, force_plaintext == DC_FP_ADD_AUTOCRYPT_HEADER, @@ -738,12 +723,11 @@ pub unsafe fn dc_mimefactory_render( min_verified, do_gossip, message, - ); - } - if e2ee_helper.encryption_successfull { - factory.out_encrypted = true; - if do_gossip { - factory.out_gossiped = true; + )? { + factory.out_encrypted = true; + if do_gossip { + factory.out_gossiped = true; + } } } factory.out = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char); @@ -781,48 +765,6 @@ fn get_subject( } } -pub fn add_mailimf_field(fields: *mut mailimf_fields, name: &str, value: &str) { - unsafe { - let field = mailimf_field_new_custom(name.strdup(), value.strdup()); - let res = mailimf_fields_add(fields, field); - assert!( - res as u32 == MAILIMF_NO_ERROR, - "could not create mailimf field" - ); - } -} - -fn build_body_text(text: &str) -> *mut mailmime { - let mime_fields: *mut mailmime_fields; - let message_part: *mut mailmime; - let content: *mut mailmime_content; - unsafe { - content = - mailmime_content_new_with_str(b"text/plain\x00" as *const u8 as *const libc::c_char); - clist_insert_after( - (*content).ct_parameters, - (*(*content).ct_parameters).last, - mailmime_param_new_with_data( - b"charset\x00" as *const u8 as *const libc::c_char as *mut libc::c_char, - b"utf-8\x00" as *const u8 as *const libc::c_char as *mut libc::c_char, - ) as *mut libc::c_void, - ); - mime_fields = mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int); - message_part = mailmime_new_empty(content, mime_fields); - } - set_body_text(message_part, text); - - message_part -} - -fn set_body_text(part: *mut mailmime, text: &str) { - use libc::strlen; - unsafe { - let text_c = text.strdup(); - mailmime_set_body_text(part, text_c, strlen(text_c)); - } -} - #[allow(non_snake_case)] fn build_body_file(context: &Context, msg: &Message, base_name: &str) -> (*mut mailmime, String) { let path_filename = match msg.param.get(Param::File) { @@ -921,7 +863,7 @@ fn build_body_file(context: &Context, msg: &Message, base_name: &str) -> (*mut m } } } - let content = mailmime_content_new_with_str(mimetype.strdup()); + let content = new_mailmime_content_type(&mimetype); let filename_encoded = dc_encode_header_words(&filename_to_send).strdup(); clist_insert_after( (*content).ct_parameters, diff --git a/src/e2ee.rs b/src/e2ee.rs index 6f12fc7d6..8a7e3f86a 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -31,10 +31,11 @@ use crate::keyring::*; use crate::peerstate::*; use crate::pgp::*; use crate::securejoin::handle_degrade_event; +use crate::wrapmime; +use crate::wrapmime::*; #[derive(Debug, Default)] pub struct E2eeHelper { - pub encryption_successfull: bool, cdata_to_free: Option>, pub encrypted: bool, pub signatures: HashSet, @@ -59,289 +60,248 @@ impl E2eeHelper { min_verified: libc::c_int, do_gossip: bool, mut in_out_message: *mut mailmime, - ) { - let mut ok_to_continue = true; + ) -> Result { let mut col: libc::c_int = 0i32; - let mut do_encrypt: libc::c_int = 0i32; - /*just a pointer into mailmime structure, must not be freed*/ - let imffields_unprotected: *mut mailimf_fields; + let mut do_encrypt = false; let mut keyring = Keyring::default(); - let plain: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char); let mut peerstates: Vec = Vec::new(); - if !(in_out_message.is_null() || !(*in_out_message).mm_parent.is_null() || plain.is_null()) + if in_out_message.is_null() || !(*in_out_message).mm_parent.is_null() { + bail!("invalid inputs"); + } + + let addr = match context.get_config(Config::ConfiguredAddr) { + Some(addr) => addr, + None => { + bail!("addr not configured"); + } + }; + + let public_key = match load_or_generate_self_public_key(context, &addr) { + Err(err) => { + bail!("Failed to load own public key: {}", err); + } + Ok(public_key) => public_key + }; + + /* libEtPan's pgp_encrypt_mime() takes the parent as the new root. We just expect the root as being given to this function. */ + let prefer_encrypt = if 0 + != context + .sql + .get_config_int(context, "e2ee_enabled") + .unwrap_or_default() { - /* libEtPan's pgp_encrypt_mime() takes the parent as the new root. We just expect the root as being given to this function. */ - let prefer_encrypt = if 0 - != context - .sql - .get_config_int(context, "e2ee_enabled") - .unwrap_or_default() - { - EncryptPreference::Mutual - } else { - EncryptPreference::NoPreference - }; + EncryptPreference::Mutual + } else { + EncryptPreference::NoPreference + }; - let addr = context.get_config(Config::ConfiguredAddr); - if let Some(addr) = addr { - let pubkey_ret = load_or_generate_self_public_key(context, &addr).map_err(|err| { - error!(context, "Failed to load public key: {}", err); - err - }); - if let Ok(public_key) = pubkey_ret { - /*only for random-seed*/ - if prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed { - do_encrypt = 1i32; - for recipient_addr in recipients_addr.iter() { - if *recipient_addr != addr { - let peerstate = - Peerstate::from_addr(context, &context.sql, &recipient_addr); - if peerstate.is_some() - && (peerstate.as_ref().unwrap().prefer_encrypt - == EncryptPreference::Mutual - || e2ee_guaranteed) - { - let peerstate = peerstate.unwrap(); - info!( - context, - "dc_e2ee_encrypt {} has peerstate", recipient_addr - ); - if let Some(key) = peerstate.peek_key(min_verified as usize) { - keyring.add_owned(key.clone()); - peerstates.push(peerstate); - } - } else { - info!( - context, - "dc_e2ee_encrypt {} HAS NO peerstate {}", - recipient_addr, - peerstate.is_some() - ); - do_encrypt = 0i32; - /* if we cannot encrypt to a single recipient, we cannot encrypt the message at all */ - break; - } + let plain: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char); + /*only for random-seed*/ + if prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed { + do_encrypt = true; + for recipient_addr in recipients_addr.iter() { + if *recipient_addr != addr { + let peerstate = + Peerstate::from_addr(context, &context.sql, &recipient_addr); + if peerstate.is_some() + && (peerstate.as_ref().unwrap().prefer_encrypt + == EncryptPreference::Mutual + || e2ee_guaranteed) + { + let peerstate = peerstate.unwrap(); + info!(context, "dc_e2ee_encrypt {} has peerstate", recipient_addr); + if let Some(key) = peerstate.peek_key(min_verified as usize) { + keyring.add_owned(key.clone()); + peerstates.push(peerstate); } - } - } - let sign_key = if 0 != do_encrypt { - keyring.add_ref(&public_key); - let key = Key::from_self_private(context, addr.clone(), &context.sql); - - if key.is_none() { - do_encrypt = 0i32; - } - key - } else { - None - }; - if force_unencrypted { - do_encrypt = 0i32 - } - imffields_unprotected = mailmime_find_mailimf_fields(in_out_message); - if !imffields_unprotected.is_null() { - /* encrypt message, if possible */ - if 0 != do_encrypt { - mailprivacy_prepare_mime(in_out_message); - let mut part_to_encrypt: *mut mailmime = - (*in_out_message).mm_data.mm_message.mm_msg_mime; - (*part_to_encrypt).mm_parent = ptr::null_mut(); - let imffields_encrypted: *mut mailimf_fields = - mailimf_fields_new_empty(); - /* mailmime_new_message_data() calls mailmime_fields_new_with_version() which would add the unwanted MIME-Version:-header */ - let message_to_encrypt: *mut mailmime = mailmime_new( - MAILMIME_MESSAGE as libc::c_int, - ptr::null(), - 0 as libc::size_t, - mailmime_fields_new_empty(), - mailmime_get_content_message(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - imffields_encrypted, - part_to_encrypt, - ); - if do_gossip { - let i_cnt = peerstates.len() as libc::c_int; - if i_cnt > 1 { - let mut i = 0; - while i < i_cnt { - let p = peerstates[i as usize] - .render_gossip_header(min_verified as usize); - - if let Some(header) = p { - mailimf_fields_add( - imffields_encrypted, - mailimf_field_new_custom( - "Autocrypt-Gossip".strdup(), - header.strdup(), - ), - ); - } - i += 1 - } - } - } - /* memoryhole headers */ - // XXX we can't use clist's into_iter() - // because the loop body also removes items - let mut cur: *mut clistiter = - (*(*imffields_unprotected).fld_list).first; - while !cur.is_null() { - let field: *mut mailimf_field = (*cur).data as *mut mailimf_field; - let mut move_to_encrypted = false; - if !field.is_null() { - if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int { - move_to_encrypted = true; - } else if (*field).fld_type - == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int - { - let opt_field = (*field).fld_data.fld_optional_field; - if !opt_field.is_null() && !(*opt_field).fld_name.is_null() - { - let fld_name = to_string_lossy((*opt_field).fld_name); - if fld_name.starts_with("Secure-Join") - || fld_name.starts_with("Chat-") - { - move_to_encrypted = true; - } - } - } - } - if move_to_encrypted { - mailimf_fields_add(imffields_encrypted, field); - cur = clist_delete((*imffields_unprotected).fld_list, cur); - } else { - cur = (*cur).next; - } - } - let subject: *mut mailimf_subject = mailimf_subject_new("...".strdup()); - mailimf_fields_add( - imffields_unprotected, - mailimf_field_new( - MAILIMF_FIELD_SUBJECT as libc::c_int, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - subject, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ), - ); - clist_insert_after( - (*(*part_to_encrypt).mm_content_type).ct_parameters, - (*(*(*part_to_encrypt).mm_content_type).ct_parameters).last, - mailmime_param_new_with_data( - b"protected-headers\x00" as *const u8 as *const libc::c_char - as *mut libc::c_char, - b"v1\x00" as *const u8 as *const libc::c_char - as *mut libc::c_char, - ) as *mut libc::c_void, - ); - mailmime_write_mem(plain, &mut col, message_to_encrypt); - if (*plain).str_0.is_null() || (*plain).len <= 0 { - ok_to_continue = false; - } else { - if let Ok(ctext_v) = dc_pgp_pk_encrypt( - std::slice::from_raw_parts( - (*plain).str_0 as *const u8, - (*plain).len, - ), - &keyring, - sign_key.as_ref(), - ) { - let ctext_bytes = ctext_v.len(); - let ctext = ctext_v.strdup(); - self.cdata_to_free = Some(Box::new(ctext)); - - /* create MIME-structure that will contain the encrypted text */ - let mut encrypted_part: *mut mailmime = new_data_part( - ptr::null_mut(), - 0 as libc::size_t, - b"multipart/encrypted\x00" as *const u8 - as *const libc::c_char - as *mut libc::c_char, - -1i32, - ); - let content: *mut mailmime_content = - (*encrypted_part).mm_content_type; - clist_insert_after( - (*content).ct_parameters, - (*(*content).ct_parameters).last, - mailmime_param_new_with_data( - b"protocol\x00" as *const u8 as *const libc::c_char - as *mut libc::c_char, - b"application/pgp-encrypted\x00" as *const u8 - as *const libc::c_char - as *mut libc::c_char, - ) - as *mut libc::c_void, - ); - static mut VERSION_CONTENT: [libc::c_char; 13] = - [86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0]; - let version_mime: *mut mailmime = new_data_part( - VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void, - strlen(VERSION_CONTENT.as_mut_ptr()), - b"application/pgp-encrypted\x00" as *const u8 - as *const libc::c_char - as *mut libc::c_char, - MAILMIME_MECHANISM_7BIT as libc::c_int, - ); - mailmime_smart_add_part(encrypted_part, version_mime); - let ctext_part: *mut mailmime = new_data_part( - ctext as *mut libc::c_void, - ctext_bytes, - b"application/octet-stream\x00" as *const u8 - as *const libc::c_char - as *mut libc::c_char, - MAILMIME_MECHANISM_7BIT as libc::c_int, - ); - mailmime_smart_add_part(encrypted_part, ctext_part); - (*in_out_message).mm_data.mm_message.mm_msg_mime = - encrypted_part; - (*encrypted_part).mm_parent = in_out_message; - mailmime_free(message_to_encrypt); - self.encryption_successfull = true; - } - } - } - if ok_to_continue { - let aheader = Aheader::new(addr, public_key, prefer_encrypt); - mailimf_fields_add( - imffields_unprotected, - mailimf_field_new_custom( - "Autocrypt".strdup(), - aheader.to_string().strdup(), - ), + } else { + info!( + context, + "dc_e2ee_encrypt {} HAS NO peerstate {}", + recipient_addr, + peerstate.is_some() ); + do_encrypt = false; + /* if we cannot encrypt to a single recipient, we cannot encrypt the message at all */ + break; } } } } - } + let sign_key = if do_encrypt { + keyring.add_ref(&public_key); + let key = Key::from_self_private(context, addr.clone(), &context.sql); + if key.is_none() { + do_encrypt = false; + } + key + } else { + None + }; + if force_unencrypted { + do_encrypt = false; + } + /*just a pointer into mailmime structure, must not be freed*/ + let imffields_unprotected = mailmime_find_mailimf_fields(in_out_message); + if !imffields_unprotected.is_null() { + /* encrypt message, if possible */ + if do_encrypt { + mailprivacy_prepare_mime(in_out_message); + let mut part_to_encrypt: *mut mailmime = + (*in_out_message).mm_data.mm_message.mm_msg_mime; + (*part_to_encrypt).mm_parent = ptr::null_mut(); + let imffields_encrypted: *mut mailimf_fields = mailimf_fields_new_empty(); + /* mailmime_new_message_data() calls mailmime_fields_new_with_version() which would add the unwanted MIME-Version:-header */ + let message_to_encrypt: *mut mailmime = mailmime_new( + MAILMIME_MESSAGE as libc::c_int, + ptr::null(), + 0 as libc::size_t, + mailmime_fields_new_empty(), + mailmime_get_content_message(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + imffields_encrypted, + part_to_encrypt, + ); + if do_gossip { + for peerstate in peerstates { + peerstate.render_gossip_header(min_verified as usize).map(|header| { + wrapmime::new_custom_field( + imffields_encrypted, + "Autocrypt-Gossip", + &header + ) + }); + } + } + /* memoryhole headers */ + // XXX we can't use clist's into_iter() + // because the loop body also removes items + let mut cur: *mut clistiter = (*(*imffields_unprotected).fld_list).first; + while !cur.is_null() { + let field: *mut mailimf_field = (*cur).data as *mut mailimf_field; + let mut move_to_encrypted = false; + if !field.is_null() { + if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int { + move_to_encrypted = true; + } else if (*field).fld_type + == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int + { + let opt_field = (*field).fld_data.fld_optional_field; + if !opt_field.is_null() && !(*opt_field).fld_name.is_null() { + let fld_name = to_string_lossy((*opt_field).fld_name); + if fld_name.starts_with("Secure-Join") + || fld_name.starts_with("Chat-") + { + move_to_encrypted = true; + } + } + } + } + if move_to_encrypted { + mailimf_fields_add(imffields_encrypted, field); + cur = clist_delete((*imffields_unprotected).fld_list, cur); + } else { + cur = (*cur).next; + } + } + let subject: *mut mailimf_subject = mailimf_subject_new("...".strdup()); + mailimf_fields_add( + imffields_unprotected, + mailimf_field_new( + MAILIMF_FIELD_SUBJECT as libc::c_int, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + subject, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ), + ); + wrapmime::append_ct_param( + (*part_to_encrypt).mm_content_type, + "protected-headers", + "v1", + )?; + mailmime_write_mem(plain, &mut col, message_to_encrypt); + if (*plain).str_0.is_null() || (*plain).len <= 0 { + bail!("could not write/allocate"); + } + if let Ok(ctext_v) = dc_pgp_pk_encrypt( + std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len), + &keyring, + sign_key.as_ref(), + ) { + let ctext_bytes = ctext_v.len(); + let ctext = ctext_v.strdup(); + self.cdata_to_free = Some(Box::new(ctext)); + + /* create MIME-structure that will contain the encrypted text */ + let mut encrypted_part: *mut mailmime = new_data_part( + ptr::null_mut(), + 0 as libc::size_t, + "multipart/encrypted", + -1, + ); + let content: *mut mailmime_content = (*encrypted_part).mm_content_type; + wrapmime::append_ct_param( + content, + "protocol", + "application/pgp-encrypted", + )?; + static mut VERSION_CONTENT: [libc::c_char; 13] = + [86, 101, 114, 115, 105, 111, 110, 58, 32, 49, 13, 10, 0]; + let version_mime: *mut mailmime = new_data_part( + VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void, + strlen(VERSION_CONTENT.as_mut_ptr()), + "application/pgp-encrypted", + MAILMIME_MECHANISM_7BIT as i32, + ); + mailmime_smart_add_part(encrypted_part, version_mime); + let ctext_part: *mut mailmime = new_data_part( + ctext as *mut libc::c_void, + ctext_bytes, + "application/octet-stream", + MAILMIME_MECHANISM_7BIT as i32, + ); + mailmime_smart_add_part(encrypted_part, ctext_part); + (*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part; + (*encrypted_part).mm_parent = in_out_message; + mailmime_free(message_to_encrypt); + if !plain.is_null() { + mmap_string_free(plain); + } + return Ok(true); + } + } + let aheader = Aheader::new(addr, public_key, prefer_encrypt).to_string(); + new_custom_field(imffields_unprotected, "Autocrypt", &aheader); + } if !plain.is_null() { mmap_string_free(plain); } + Ok(false) } pub unsafe fn decrypt(&mut self, context: &Context, in_out_message: *mut mailmime) { @@ -464,28 +424,23 @@ impl E2eeHelper { unsafe fn new_data_part( data: *mut libc::c_void, data_bytes: libc::size_t, - default_content_type: *mut libc::c_char, + default_content_type: &str, default_encoding: libc::c_int, ) -> *mut mailmime { let mut ok_to_continue = true; - //char basename_buf[PATH_MAX]; - let mut encoding: *mut mailmime_mechanism; + let mut encoding: *mut mailmime_mechanism = ptr::null_mut(); let content: *mut mailmime_content; let mime: *mut mailmime; - //int r; - //char * dup_filename; let mime_fields: *mut mailmime_fields; let encoding_type: libc::c_int; - let content_type_str: *mut libc::c_char; let mut do_encoding: libc::c_int; - encoding = ptr::null_mut(); - if default_content_type.is_null() { - content_type_str = - b"application/octet-stream\x00" as *const u8 as *const libc::c_char as *mut libc::c_char + + let content_type = if default_content_type.is_empty() { + "application/octet-stream" } else { - content_type_str = default_content_type - } - content = mailmime_content_new_with_str(content_type_str); + default_content_type + }; + content = new_mailmime_content_type(&content_type); if content.is_null() { ok_to_continue = false; } else { diff --git a/src/lib.rs b/src/lib.rs index 87cf915be..4b9c8aec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,8 @@ pub mod dc_tools; mod login_param; pub mod securejoin; mod token; +#[macro_use] +pub(crate) mod wrapmime; #[cfg(test)] mod test_utils; diff --git a/src/wrapmime.rs b/src/wrapmime.rs new file mode 100644 index 000000000..3668866ca --- /dev/null +++ b/src/wrapmime.rs @@ -0,0 +1,119 @@ +use std::ffi::CString; + +use crate::dc_tools::*; +use crate::error::Error; +use mmime::clist::*; +use mmime::mailimf_types::*; +use mmime::mailimf_types_helper::*; +use mmime::mailmime_disposition::*; +use mmime::mailmime_types::*; +use mmime::mailmime_types_helper::*; +use mmime::other::*; + +#[macro_export] +macro_rules! clist_append { + ($clist:expr, $item:expr) => { + if clist_insert_after( + $clist as *mut clist, + (*$clist).last, + $item as *mut libc::c_void, + ) != 0 + { + bail!("could not allocate or append list item"); + } + }; +} + +pub fn add_filename_part( + message: *mut mailmime, + basename: &str, + mime_type: &str, + file_content: &str, +) -> Result<(), Error> { + let mime_type_c = CString::new(mime_type.to_string()).expect("failed to create CString"); + unsafe { + let content_type = mailmime_content_new_with_str(mime_type_c.as_ptr()); + let mime_fields = mailmime_fields_new_filename( + MAILMIME_DISPOSITION_TYPE_ATTACHMENT as libc::c_int, + basename.strdup(), + MAILMIME_MECHANISM_8BIT as libc::c_int, + ); + let file_mime_part = mailmime_new_empty(content_type, mime_fields); + set_body_text(file_mime_part, file_content)?; + mailmime_smart_add_part(message, file_mime_part); + } + Ok(()) +} + +pub fn new_custom_field(fields: *mut mailimf_fields, name: &str, value: &str) { + unsafe { + let field = mailimf_field_new_custom(name.strdup(), value.strdup()); + let res = mailimf_fields_add(fields, field); + assert!( + res as u32 == MAILIMF_NO_ERROR, + "could not create mailimf field" + ); + } +} + + +pub fn build_body_text(text: &str) -> Result<*mut mailmime, Error> { + let mime_fields: *mut mailmime_fields; + let message_part: *mut mailmime; + + let content = new_mailmime_content_type("text/plain"); + append_ct_param(content, "charset", "utf-8")?; + + unsafe { + mime_fields = mailmime_fields_new_encoding(MAILMIME_MECHANISM_8BIT as libc::c_int); + message_part = mailmime_new_empty(content, mime_fields); + } + set_body_text(message_part, text)?; + + Ok(message_part) +} + +pub fn append_ct_param( + content: *mut mailmime_content, + name: &str, + value: &str, +) -> Result<(), Error> { + unsafe { + let name_c = CString::new(name).unwrap().as_ptr(); + let value_c = CString::new(value).unwrap().as_ptr(); + + clist_append!( + (*content).ct_parameters, + mailmime_param_new_with_data( + name_c as *const u8 as *const libc::c_char as *mut libc::c_char, + value_c as *const u8 as *const libc::c_char as *mut libc::c_char + ) + ); + } + Ok(()) +} + +pub fn new_mailmime_content_type(content_type: &str) -> *mut mailmime_content { + let ct = CString::new(content_type).unwrap(); + let content: *mut mailmime_content; + // mailmime_content_new_with_str only parses but does not retain/own ct + // + unsafe { + content = mailmime_content_new_with_str(ct.as_ptr()); + } + if content.is_null() { + panic!("mailimf failed to allocate"); + } + content +} + +pub fn set_body_text(part: *mut mailmime, text: &str) -> Result<(), Error> { + use libc::strlen; + unsafe { + let text_c = text.strdup(); + if 0 != mailmime_set_body_text(part, text_c, strlen(text_c)) { + bail!("could not set body text on mime-structure"); + } + } + Ok(()) +}