diff --git a/src/dc_receive_imf.rs b/src/dc_receive_imf.rs index b014377e8..bf96b159f 100644 --- a/src/dc_receive_imf.rs +++ b/src/dc_receive_imf.rs @@ -116,8 +116,7 @@ pub fn dc_receive_imf( if let Some(value) = mime_parser.lookup_field("Date") { // is not yet checked against bad times! we do this later if we have the database information. - // sent_timestamp = dc_timestamp_from_date((*orig_date).dt_date_time) - // TODO + sent_timestamp = mailparse::dateparse(value).unwrap_or_default(); } // get From: and check if it is known (for known From:'s we add the other To:/Cc: in the 3rd pass) diff --git a/src/e2ee.rs b/src/e2ee.rs index 4f6b6ce06..8e1931630 100644 --- a/src/e2ee.rs +++ b/src/e2ee.rs @@ -137,14 +137,14 @@ impl EncryptHelper { pub fn try_decrypt( context: &Context, mail: &mailparse::ParsedMail<'_>, -) -> Result<(Option>, HashSet, HashSet)> { +) -> Result<(Option>, HashSet, i64)> { use mailparse::MailHeaderMap; let from = mail.headers.get_first_value("From")?.unwrap_or_default(); let message_time = mail .headers .get_first_value("Date")? - .and_then(|v| v.parse().ok()) + .and_then(|v| mailparse::dateparse(&v).ok()) .unwrap_or_default(); let mut peerstate = None; @@ -171,7 +171,6 @@ pub fn try_decrypt( let mut private_keyring = Keyring::default(); let mut public_keyring_for_validate = Keyring::default(); let mut out_mail = None; - let mut gossipped_addr = HashSet::default(); let mut signatures = HashSet::default(); let self_addr = context.get_config(Config::ConfiguredAddr); @@ -199,15 +198,9 @@ pub fn try_decrypt( &public_keyring_for_validate, &mut signatures, )?; - - // TODO: - // if !gossip_headers.is_empty() { - // gossipped_addr = - // update_gossip_peerstates(context, message_time, imffields, gossip_headers)?; - // } } } - Ok((out_mail, signatures, gossipped_addr)) + Ok((out_mail, signatures, message_time)) } /// Load public key from database or generate a new one. @@ -257,74 +250,6 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef Result> { -// // XXX split the parsing from the modification part -// let mut recipients: Option> = None; -// let mut gossipped_addr: HashSet = Default::default(); - -// for cur_data in { (*(*gossip_headers).fld_list).into_iter() } { -// let field = cur_data as *mut mailimf_field; -// if field.is_null() { -// continue; -// } - -// let field = { *field }; - -// if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int { -// let optional_field = { field.fld_data.fld_optional_field }; -// if optional_field.is_null() { -// continue; -// } - -// let optional_field = { *optional_field }; -// if !optional_field.fld_name.is_null() -// && to_string_lossy(optional_field.fld_name) == "Autocrypt-Gossip" -// { -// let value = to_string_lossy(optional_field.fld_value); -// let gossip_header = Aheader::from_str(&value); - -// if let Ok(ref header) = gossip_header { -// if recipients.is_none() { -// recipients = Some(mailimf_get_recipients(imffields)); -// } -// if recipients.as_ref().unwrap().contains(&header.addr) { -// let mut peerstate = -// Peerstate::from_addr(context, &context.sql, &header.addr); -// if let Some(ref mut peerstate) = peerstate { -// peerstate.apply_gossip(header, message_time); -// peerstate.save_to_db(&context.sql, false)?; -// } else { -// let p = Peerstate::from_gossip(context, header, message_time); -// p.save_to_db(&context.sql, true)?; -// peerstate = Some(p); -// } -// if let Some(peerstate) = peerstate { -// if peerstate.degrade_event.is_some() { -// handle_degrade_event(context, &peerstate)?; -// } -// } - -// gossipped_addr.insert(header.addr.clone()); -// } else { -// info!( -// context, -// "Ignoring gossipped \"{}\" as the address is not in To/Cc list.", -// &header.addr, -// ); -// } -// } -// } -// } -// } - -// Ok(gossipped_addr) -// } - fn decrypt_if_autocrypt_message<'a>( context: &Context, mail: &mailparse::ParsedMail<'a>, @@ -347,35 +272,13 @@ fn decrypt_if_autocrypt_message<'a>( Ok(res) => res, }; - let decrypted = decrypt_part( + decrypt_part( context, encrypted_data_part, private_keyring, public_keyring_for_validate, ret_valid_signatures, - )?; - - // finally, let's also return gossip headers - // XXX better return parsed headers so that upstream - // does not need to dive into mmime-stuff again. - // { - // if (*ret_gossip_headers).is_null() && !ret_valid_signatures.is_empty() { - // let mut dummy: libc::size_t = 0; - // let mut test: *mut mailimf_fields = ptr::null_mut(); - // if mailimf_envelope_and_optional_fields_parse( - // (*decrypted_mime).mm_mime_start, - // (*decrypted_mime).mm_length, - // &mut dummy, - // &mut test, - // ) == MAILIMF_NO_ERROR as libc::c_int - // && !test.is_null() - // { - // *ret_gossip_headers = test; - // } - // } - // } - - Ok(decrypted) + ) } /// Returns Ok(None) if nothing encrypted was found. diff --git a/src/mimeparser.rs b/src/mimeparser.rs index ca668810a..97ee40e5c 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -1,7 +1,9 @@ use std::collections::{HashMap, HashSet}; use deltachat_derive::{FromSql, ToSql}; +use mailparse::MailHeaderMap; +use crate::aheader::Aheader; use crate::blob::BlobObject; use crate::config::Config; use crate::constants::Viewtype; @@ -16,8 +18,9 @@ use crate::location; use crate::message; use crate::message::MsgId; use crate::param::*; +use crate::peerstate::Peerstate; +use crate::securejoin::handle_degrade_event; use crate::stock::StockMessage; -use crate::wrapmime; #[derive(Debug)] pub struct MimeParser<'a> { @@ -100,14 +103,27 @@ impl<'a> MimeParser<'a> { let mail_raw; let mail = match e2ee::try_decrypt(parser.context, &mail) { - Ok((raw, signatures, gossipped_addr)) => { + Ok((raw, signatures, message_time)) => { + // Valid autocrypt message, encrypted parser.encrypted = raw.is_some(); parser.signatures = signatures; - parser.gossipped_addr = gossipped_addr; + if let Some(raw) = raw { mail_raw = raw; - mailparse::parse_mail(&mail_raw)? + let decrypted_mail = mailparse::parse_mail(&mail_raw)?; + + // we have a decrypted mail, that is valid, check for gossip headers + + let gossip_headers = + decrypted_mail.headers.get_all_values("Autocrypt-Gossip")?; + if !gossip_headers.is_empty() { + parser.gossipped_addr = + update_gossip_peerstates(context, message_time, &mail, gossip_headers)?; + } + + decrypted_mail } else { + // Message was not encrypted mail } } @@ -726,7 +742,7 @@ impl<'a> MimeParser<'a> { } if let mailparse::MailAddr::Single(ref info) = addrs[0] { let from_addr_norm = addr_normalize(&info.addr); - let recipients = wrapmime::mailimf_get_recipients(&self.header); + let recipients = get_recipients(self.header.iter()); if recipients.len() == 1 && recipients.contains(from_addr_norm) { return true; } @@ -774,8 +790,6 @@ impl<'a> MimeParser<'a> { } fn process_report(&self, report: &mailparse::ParsedMail<'_>) -> Result> { - use mailparse::MailHeaderMap; - let ct = report.get_content_disposition()?; let report_type = ct.params.get("report-type"); if report_type.is_none() { @@ -853,6 +867,55 @@ impl<'a> MimeParser<'a> { } } +fn update_gossip_peerstates( + context: &Context, + message_time: i64, + mail: &mailparse::ParsedMail<'_>, + gossip_headers: Vec, +) -> Result> { + // XXX split the parsing from the modification part + let mut recipients: Option> = None; + let mut gossipped_addr: HashSet = Default::default(); + + for value in &gossip_headers { + let gossip_header = value.parse::(); + + if let Ok(ref header) = gossip_header { + if recipients.is_none() { + recipients = Some(get_recipients(mail.headers.iter().map(|v| { + // TODO: error handling + (v.get_key().unwrap(), v.get_value().unwrap()) + }))); + } + if recipients.as_ref().unwrap().contains(&header.addr) { + let mut peerstate = Peerstate::from_addr(context, &context.sql, &header.addr); + if let Some(ref mut peerstate) = peerstate { + peerstate.apply_gossip(header, message_time); + peerstate.save_to_db(&context.sql, false)?; + } else { + let p = Peerstate::from_gossip(context, header, message_time); + p.save_to_db(&context.sql, true)?; + peerstate = Some(p); + } + if let Some(peerstate) = peerstate { + if peerstate.degrade_event.is_some() { + handle_degrade_event(context, &peerstate)?; + } + } + + gossipped_addr.insert(header.addr.clone()); + } else { + info!( + context, + "Ignoring gossipped \"{}\" as the address is not in To/Cc list.", &header.addr, + ); + } + } + } + + Ok(gossipped_addr) +} + #[derive(Debug)] struct Report { original_message_id: String, @@ -967,6 +1030,35 @@ fn mailmime_is_attachment_disposition(mail: &mailparse::ParsedMail<'_>) -> bool false } +// returned addresses are normalized. +fn get_recipients<'a, S: AsRef, T: Iterator>(headers: T) -> HashSet { + let mut recipients: HashSet = Default::default(); + + for (hkey, hvalue) in headers { + let hkey = hkey.as_ref(); + let hvalue = hvalue.as_ref(); + + if hkey == "to" || hkey == "cc" { + if let Ok(addrs) = mailparse::addrparse(hvalue) { + for addr in addrs.iter() { + match addr { + mailparse::MailAddr::Single(ref info) => { + recipients.insert(addr_normalize(&info.addr).into()); + } + mailparse::MailAddr::Group(ref infos) => { + for info in &infos.addrs { + recipients.insert(addr_normalize(&info.addr).into()); + } + } + } + } + } + } + } + + recipients +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/securejoin.rs b/src/securejoin.rs index b22bb4fde..85d6b2cd4 100644 --- a/src/securejoin.rs +++ b/src/securejoin.rs @@ -337,325 +337,329 @@ impl Default for HandshakeMessageStatus { } } -/* library private: secure-join */ +/// Handle incoming secure-join handshake. pub(crate) fn handle_securejoin_handshake( context: &Context, mimeparser: &MimeParser, contact_id: u32, ) -> Result { - unimplemented!() - // let own_fingerprint: String; + let own_fingerprint: String; - // ensure!( - // contact_id > DC_CONTACT_ID_LAST_SPECIAL, - // "handle_securejoin_handshake(): called with special contact id" - // ); - // let step = match mimeparser.lookup_optional_field("Secure-Join") { - // Some(s) => s, - // None => { - // bail!("This message is not a Secure-Join message"); - // } - // }; - // info!( - // context, - // ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step, - // ); - // let (contact_chat_id, contact_chat_id_blocked) = - // chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default(); + ensure!( + contact_id > DC_CONTACT_ID_LAST_SPECIAL, + "handle_securejoin_handshake(): called with special contact id" + ); + let step = mimeparser + .lookup_field("Secure-Join") + .ok_or_else(|| format_err!("This message is not a Secure-Join message"))?; - // if contact_chat_id_blocked != Blocked::Not { - // chat::unblock(context, contact_chat_id); - // } - // let join_vg = step.starts_with("vg-"); - // let mut ret = HandshakeMessageStatus::default(); + info!( + context, + ">>>>>>>>>>>>>>>>>>>>>>>>> secure-join message \'{}\' received", step, + ); - // match step.as_str() { - // "vg-request" | "vc-request" => { - // /* ========================================================= - // ==== Alice - the inviter side ==== - // ==== Step 3 in "Setup verified contact" protocol ==== - // ========================================================= */ - // // this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet) - // // it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it, - // // send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here. - // // verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code - // let invitenumber = match mimeparser.lookup_optional_field("Secure-Join-Invitenumber") { - // Some(n) => n, - // None => { - // warn!(context, "Secure-join denied (invitenumber missing).",); - // return Ok(ret); - // } - // }; - // if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) { - // warn!(context, "Secure-join denied (bad invitenumber).",); - // return Ok(ret); - // } - // info!(context, "Secure-join requested.",); + let (contact_chat_id, contact_chat_id_blocked) = + chat::create_or_lookup_by_contact_id(context, contact_id, Blocked::Not).unwrap_or_default(); - // inviter_progress!(context, contact_id, 300); - // send_handshake_msg( - // context, - // contact_chat_id, - // &format!("{}-auth-required", &step[..2]), - // "", - // None, - // "", - // ); - // } - // "vg-auth-required" | "vc-auth-required" => { - // let cond = { - // let bob = context.bob.read().unwrap(); - // let scan = bob.qr_scan.as_ref(); - // scan.is_none() - // || bob.expects != DC_VC_AUTH_REQUIRED - // || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup - // }; + if contact_chat_id_blocked != Blocked::Not { + chat::unblock(context, contact_chat_id); + } - // if cond { - // warn!(context, "auth-required message out of sync.",); - // // no error, just aborted somehow or a mail from another handshake - // return Ok(ret); - // } - // let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); - // let auth = get_qr_attr!(context, auth).to_string(); + let join_vg = step.starts_with("vg-"); + let mut ret = HandshakeMessageStatus::default(); - // if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // if mimeparser.encrypted { - // "No valid signature." - // } else { - // "Not encrypted." - // }, - // ); - // ret.stop_ongoing_process = true; - // ret.bob_securejoin_success = Some(false); - // return Ok(ret); - // } - // if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Fingerprint mismatch on joiner-side.", - // ); - // ret.stop_ongoing_process = true; - // ret.bob_securejoin_success = Some(false); - // return Ok(ret); - // } - // info!(context, "Fingerprint verified.",); - // own_fingerprint = get_self_fingerprint(context).unwrap(); - // joiner_progress!(context, contact_id, 400); - // context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM; + match step.as_str() { + "vg-request" | "vc-request" => { + /* ========================================================= + ==== Alice - the inviter side ==== + ==== Step 3 in "Setup verified contact" protocol ==== + ========================================================= */ + // this message may be unencrypted (Bob, the joinder and the sender, might not have Alice's key yet) + // it just ensures, we have Bobs key now. If we do _not_ have the key because eg. MitM has removed it, + // send_message() will fail with the error "End-to-end-encryption unavailable unexpectedly.", so, there is no additional check needed here. + // verify that the `Secure-Join-Invitenumber:`-header matches invitenumber written to the QR code + let invitenumber = match mimeparser.lookup_field("Secure-Join-Invitenumber") { + Some(n) => n, + None => { + warn!(context, "Secure-join denied (invitenumber missing).",); + return Ok(ret); + } + }; + if !token::exists(context, token::Namespace::InviteNumber, &invitenumber) { + warn!(context, "Secure-join denied (bad invitenumber).",); + return Ok(ret); + } + info!(context, "Secure-join requested.",); - // send_handshake_msg( - // context, - // contact_chat_id, - // &format!("{}-request-with-auth", &step[..2]), - // auth, - // Some(own_fingerprint), - // if join_vg { - // get_qr_attr!(context, text2).to_string() - // } else { - // "".to_string() - // }, - // ); - // } - // "vg-request-with-auth" | "vc-request-with-auth" => { - // /* ============================================================ - // ==== Alice - the inviter side ==== - // ==== Steps 5+6 in "Setup verified contact" protocol ==== - // ==== Step 6 in "Out-of-band verified groups" protocol ==== - // ============================================================ */ - // // verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob - // let fingerprint = match mimeparser.lookup_optional_field("Secure-Join-Fingerprint") { - // Some(fp) => fp, - // None => { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Fingerprint not provided.", - // ); - // return Ok(ret); - // } - // }; - // if !encrypted_and_signed(mimeparser, &fingerprint) { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Auth not encrypted.", - // ); - // return Ok(ret); - // } - // if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Fingerprint mismatch on inviter-side.", - // ); - // return Ok(ret); - // } - // info!(context, "Fingerprint verified.",); - // // verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code - // let auth_0 = match mimeparser.lookup_optional_field("Secure-Join-Auth") { - // Some(auth) => auth, - // None => { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Auth not provided.", - // ); - // return Ok(ret); - // } - // }; - // if !token::exists(context, token::Namespace::Auth, &auth_0) { - // could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid."); - // return Ok(ret); - // } - // if mark_peer_as_verified(context, fingerprint).is_err() { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Fingerprint mismatch on inviter-side.", - // ); - // return Ok(ret); - // } - // Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited); - // info!(context, "Auth verified.",); - // secure_connection_established(context, contact_chat_id); - // emit_event!(context, Event::ContactsChanged(Some(contact_id))); - // inviter_progress!(context, contact_id, 600); - // if join_vg { - // let field_grpid = mimeparser - // .lookup_optional_field("Secure-Join-Group") - // .unwrap_or_default(); - // let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid); - // if group_chat_id == 0 { - // error!(context, "Chat {} not found.", &field_grpid); - // return Ok(ret); - // } else { - // if let Err(err) = - // chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) - // { - // error!(context, "failed to add contact: {}", err); - // } - // } - // } else { - // send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, ""); - // inviter_progress!(context, contact_id, 1000); - // } - // } - // "vg-member-added" | "vc-contact-confirm" => { - // if join_vg { - // ret.hide_this_msg = false; - // } - // if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM { - // info!(context, "Message belongs to a different handshake.",); - // return Ok(ret); - // } - // let cond = { - // let bob = context.bob.read().unwrap(); - // let scan = bob.qr_scan.as_ref(); - // scan.is_none() || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup - // }; - // if cond { - // warn!( - // context, - // "Message out of sync or belongs to a different handshake.", - // ); - // return Ok(ret); - // } - // let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); + inviter_progress!(context, contact_id, 300); + send_handshake_msg( + context, + contact_chat_id, + &format!("{}-auth-required", &step[..2]), + "", + None, + "", + ); + } + "vg-auth-required" | "vc-auth-required" => { + let cond = { + let bob = context.bob.read().unwrap(); + let scan = bob.qr_scan.as_ref(); + scan.is_none() + || bob.expects != DC_VC_AUTH_REQUIRED + || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup + }; - // let vg_expect_encrypted = if join_vg { - // let group_id = get_qr_attr!(context, text2).to_string(); - // let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, group_id); - // // when joining a non-verified group - // // the vg-member-added message may be unencrypted - // // when not all group members have keys or prefer encryption. - // // So only expect encryption if this is a verified group - // is_verified_group - // } else { - // // setup contact is always encrypted - // true - // }; - // if vg_expect_encrypted - // && !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) - // { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Contact confirm message not encrypted.", - // ); - // ret.bob_securejoin_success = Some(false); - // return Ok(ret); - // } + if cond { + warn!(context, "auth-required message out of sync.",); + // no error, just aborted somehow or a mail from another handshake + return Ok(ret); + } + let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); + let auth = get_qr_attr!(context, auth).to_string(); - // if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() { - // could_not_establish_secure_connection( - // context, - // contact_chat_id, - // "Fingerprint mismatch on joiner-side.", - // ); - // return Ok(ret); - // } - // Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined); - // emit_event!(context, Event::ContactsChanged(None)); - // let cg_member_added = mimeparser - // .lookup_optional_field("Chat-Group-Member-Added") - // .unwrap_or_default(); - // if join_vg && !addr_equals_self(context, cg_member_added) { - // info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); - // return Ok(ret); - // } - // secure_connection_established(context, contact_chat_id); - // context.bob.write().unwrap().expects = 0; - // if join_vg { - // send_handshake_msg( - // context, - // contact_chat_id, - // "vg-member-added-received", - // "", - // None, - // "", - // ); - // } - // ret.stop_ongoing_process = true; - // ret.bob_securejoin_success = Some(true); - // } - // "vg-member-added-received" => { - // /* ============================================================ - // ==== Alice - the inviter side ==== - // ==== Step 8 in "Out-of-band verified groups" protocol ==== - // ============================================================ */ - // if let Ok(contact) = Contact::get_by_id(context, contact_id) { - // if contact.is_verified(context) == VerifiedStatus::Unverified { - // warn!(context, "vg-member-added-received invalid.",); - // return Ok(ret); - // } - // inviter_progress!(context, contact_id, 800); - // inviter_progress!(context, contact_id, 1000); - // let field_grpid = mimeparser - // .lookup_optional_field("Secure-Join-Group") - // .unwrap_or_default(); - // let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid); - // context.call_cb(Event::SecurejoinMemberAdded { - // chat_id: group_chat_id, - // contact_id: contact_id, - // }); - // } else { - // warn!(context, "vg-member-added-received invalid.",); - // return Ok(ret); - // } - // } - // _ => { - // warn!(context, "invalid step: {}", step); - // } - // } - // if ret.hide_this_msg { - // ret.delete_this_msg = true; - // } - // Ok(ret) + if !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) { + could_not_establish_secure_connection( + context, + contact_chat_id, + if mimeparser.encrypted { + "No valid signature." + } else { + "Not encrypted." + }, + ); + ret.stop_ongoing_process = true; + ret.bob_securejoin_success = Some(false); + return Ok(ret); + } + if !fingerprint_equals_sender(context, &scanned_fingerprint_of_alice, contact_chat_id) { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Fingerprint mismatch on joiner-side.", + ); + ret.stop_ongoing_process = true; + ret.bob_securejoin_success = Some(false); + return Ok(ret); + } + info!(context, "Fingerprint verified.",); + own_fingerprint = get_self_fingerprint(context).unwrap(); + joiner_progress!(context, contact_id, 400); + context.bob.write().unwrap().expects = DC_VC_CONTACT_CONFIRM; + + send_handshake_msg( + context, + contact_chat_id, + &format!("{}-request-with-auth", &step[..2]), + auth, + Some(own_fingerprint), + if join_vg { + get_qr_attr!(context, text2).to_string() + } else { + "".to_string() + }, + ); + } + "vg-request-with-auth" | "vc-request-with-auth" => { + /* ============================================================ + ==== Alice - the inviter side ==== + ==== Steps 5+6 in "Setup verified contact" protocol ==== + ==== Step 6 in "Out-of-band verified groups" protocol ==== + ============================================================ */ + // verify that Secure-Join-Fingerprint:-header matches the fingerprint of Bob + let fingerprint = match mimeparser.lookup_field("Secure-Join-Fingerprint") { + Some(fp) => fp, + None => { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Fingerprint not provided.", + ); + return Ok(ret); + } + }; + if !encrypted_and_signed(mimeparser, &fingerprint) { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Auth not encrypted.", + ); + return Ok(ret); + } + if !fingerprint_equals_sender(context, &fingerprint, contact_chat_id) { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Fingerprint mismatch on inviter-side.", + ); + return Ok(ret); + } + info!(context, "Fingerprint verified.",); + // verify that the `Secure-Join-Auth:`-header matches the secret written to the QR code + let auth_0 = match mimeparser.lookup_field("Secure-Join-Auth") { + Some(auth) => auth, + None => { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Auth not provided.", + ); + return Ok(ret); + } + }; + if !token::exists(context, token::Namespace::Auth, &auth_0) { + could_not_establish_secure_connection(context, contact_chat_id, "Auth invalid."); + return Ok(ret); + } + if mark_peer_as_verified(context, fingerprint).is_err() { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Fingerprint mismatch on inviter-side.", + ); + return Ok(ret); + } + Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinInvited); + info!(context, "Auth verified.",); + secure_connection_established(context, contact_chat_id); + emit_event!(context, Event::ContactsChanged(Some(contact_id))); + inviter_progress!(context, contact_id, 600); + if join_vg { + let field_grpid = mimeparser + .lookup_field("Secure-Join-Group") + .map(|s| s.as_str()) + .unwrap_or_else(|| ""); + let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, field_grpid); + if group_chat_id == 0 { + error!(context, "Chat {} not found.", &field_grpid); + return Ok(ret); + } else { + if let Err(err) = + chat::add_contact_to_chat_ex(context, group_chat_id, contact_id, true) + { + error!(context, "failed to add contact: {}", err); + } + } + } else { + send_handshake_msg(context, contact_chat_id, "vc-contact-confirm", "", None, ""); + inviter_progress!(context, contact_id, 1000); + } + } + "vg-member-added" | "vc-contact-confirm" => { + if join_vg { + ret.hide_this_msg = false; + } + if context.bob.read().unwrap().expects != DC_VC_CONTACT_CONFIRM { + info!(context, "Message belongs to a different handshake.",); + return Ok(ret); + } + let cond = { + let bob = context.bob.read().unwrap(); + let scan = bob.qr_scan.as_ref(); + scan.is_none() || join_vg && scan.unwrap().state != LotState::QrAskVerifyGroup + }; + if cond { + warn!( + context, + "Message out of sync or belongs to a different handshake.", + ); + return Ok(ret); + } + let scanned_fingerprint_of_alice = get_qr_attr!(context, fingerprint).to_string(); + + let vg_expect_encrypted = if join_vg { + let group_id = get_qr_attr!(context, text2).to_string(); + let (_, is_verified_group, _) = chat::get_chat_id_by_grpid(context, group_id); + // when joining a non-verified group + // the vg-member-added message may be unencrypted + // when not all group members have keys or prefer encryption. + // So only expect encryption if this is a verified group + is_verified_group + } else { + // setup contact is always encrypted + true + }; + if vg_expect_encrypted + && !encrypted_and_signed(mimeparser, &scanned_fingerprint_of_alice) + { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Contact confirm message not encrypted.", + ); + ret.bob_securejoin_success = Some(false); + return Ok(ret); + } + + if mark_peer_as_verified(context, &scanned_fingerprint_of_alice).is_err() { + could_not_establish_secure_connection( + context, + contact_chat_id, + "Fingerprint mismatch on joiner-side.", + ); + return Ok(ret); + } + Contact::scaleup_origin_by_id(context, contact_id, Origin::SecurejoinJoined); + emit_event!(context, Event::ContactsChanged(None)); + let cg_member_added = mimeparser + .lookup_field("Chat-Group-Member-Added") + .map(|s| s.as_str()) + .unwrap_or_else(|| ""); + if join_vg && !addr_equals_self(context, cg_member_added) { + info!(context, "Message belongs to a different handshake (scaled up contact anyway to allow creation of group)."); + return Ok(ret); + } + secure_connection_established(context, contact_chat_id); + context.bob.write().unwrap().expects = 0; + if join_vg { + send_handshake_msg( + context, + contact_chat_id, + "vg-member-added-received", + "", + None, + "", + ); + } + ret.stop_ongoing_process = true; + ret.bob_securejoin_success = Some(true); + } + "vg-member-added-received" => { + /* ============================================================ + ==== Alice - the inviter side ==== + ==== Step 8 in "Out-of-band verified groups" protocol ==== + ============================================================ */ + if let Ok(contact) = Contact::get_by_id(context, contact_id) { + if contact.is_verified(context) == VerifiedStatus::Unverified { + warn!(context, "vg-member-added-received invalid.",); + return Ok(ret); + } + inviter_progress!(context, contact_id, 800); + inviter_progress!(context, contact_id, 1000); + let field_grpid = mimeparser + .lookup_field("Secure-Join-Group") + .map(|s| s.as_str()) + .unwrap_or_else(|| ""); + let (group_chat_id, _, _) = chat::get_chat_id_by_grpid(context, &field_grpid); + context.call_cb(Event::SecurejoinMemberAdded { + chat_id: group_chat_id, + contact_id: contact_id, + }); + } else { + warn!(context, "vg-member-added-received invalid.",); + return Ok(ret); + } + } + _ => { + warn!(context, "invalid step: {}", step); + } + } + + if ret.hide_this_msg { + ret.delete_this_msg = true; + } + + Ok(ret) } fn secure_connection_established(context: &Context, contact_chat_id: u32) { diff --git a/src/wrapmime.rs b/src/wrapmime.rs index ea37cdb12..4f4b6562d 100644 --- a/src/wrapmime.rs +++ b/src/wrapmime.rs @@ -1,14 +1,7 @@ -use std::collections::{HashMap, HashSet}; - use mailparse::ParsedMail; -use crate::contact::addr_normalize; use crate::error::Error; -/************************************** -* mime parsing API -**************************************/ - pub fn parse_message_id(message_id: &[u8]) -> Result { let value = std::str::from_utf8(message_id)?; let addrs = mailparse::addrparse(value) @@ -18,10 +11,7 @@ pub fn parse_message_id(message_id: &[u8]) -> Result { return Ok(info.addr); } - bail!( - "could not parse message_id: {}", - String::from_utf8_lossy(message_id) - ); + bail!("could not parse message_id: {}", value); } /// Returns a reference to the encrypted payload and validates the autocrypt structure. @@ -48,181 +38,10 @@ pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a Parsed Ok(&mail.subparts[1]) } -// returned addresses are normalized. -pub fn mailimf_get_recipients(headers: &HashMap) -> HashSet { - let mut recipients: HashSet = Default::default(); - - for (hkey, hvalue) in headers.iter() { - if hkey == "to" || hkey == "cc" { - if let Ok(addrs) = mailparse::addrparse(hvalue) { - for addr in addrs.iter() { - match addr { - mailparse::MailAddr::Single(ref info) => { - recipients.insert(addr_normalize(&info.addr).into()); - } - mailparse::MailAddr::Group(ref infos) => { - for info in &infos.addrs { - recipients.insert(addr_normalize(&info.addr).into()); - } - } - } - } - } - } - } - - recipients -} - -/************************************** -* mime creation API -**************************************/ - -// 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"); -// { -// 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) { -// { -// 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_content_type("text/plain")?; -// append_ct_param(content, "charset", "utf-8")?; - -// { -// 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> { -// { -// let name_c = CString::new(name).unwrap_or_default(); -// let value_c = CString::new(value).unwrap_or_default(); - -// clist_append!( -// (*content).ct_parameters, -// mailmime_param_new_with_data( -// name_c.as_ptr() as *const u8 as *const libc::c_char as *mut libc::c_char, -// value_c.as_ptr() as *const u8 as *const libc::c_char as *mut libc::c_char -// ) -// ); -// } -// Ok(()) -// } - -// pub fn new_content_type(content_type: &str) -> Result<*mut mailmime_content, Error> { -// let ct = CString::new(content_type).unwrap_or_default(); -// let content: *mut mailmime_content; -// // mailmime_content_new_with_str only parses but does not retain/own ct -// { -// content = mailmime_content_new_with_str(ct.as_ptr()); -// } -// ensure!(!content.is_null(), "mailimf failed to allocate"); -// Ok(content) -// } - -// pub fn set_body_text(part: *mut Mailmime, text: &str) -> Result<(), Error> { -// use libc::strlen; -// { -// 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(()) -// } - -// pub fn content_type_needs_encoding(content: *const mailmime_content) -> bool { -// { -// if (*(*content).ct_type).tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int { -// let composite = (*(*content).ct_type).tp_data.tp_composite_type; -// match (*composite).ct_type as u32 { -// MAILMIME_COMPOSITE_TYPE_MESSAGE => { -// to_string_lossy((*content).ct_subtype) != "rfc822" -// } -// MAILMIME_COMPOSITE_TYPE_MULTIPART => false, -// _ => false, -// } -// } else { -// true -// } -// } -// } - -// pub fn new_mailbox_list(displayname: &str, addr: &str) -> *mut mailimf_mailbox_list { -// let mbox: *mut mailimf_mailbox_list = { mailimf_mailbox_list_new_empty() }; -// { -// mailimf_mailbox_list_add( -// mbox, -// mailimf_mailbox_new( -// if !displayname.is_empty() { -// dc_encode_header_words(&displayname).strdup() -// } else { -// ptr::null_mut() -// }, -// addr.strdup(), -// ), -// ); -// } -// mbox -// } - #[cfg(test)] mod tests { use super::*; - // #[test] - // fn test_needs_encoding() { - // assert!(content_type_needs_encoding( - // new_content_type("text/plain").unwrap() - // )); - // assert!(content_type_needs_encoding( - // new_content_type("application/octect-stream").unwrap() - // )); - // assert!(!content_type_needs_encoding( - // new_content_type("multipart/encrypted").unwrap() - // )); - // assert!(content_type_needs_encoding( - // new_content_type("application/pgp-encrypted").unwrap() - // )); - // } - #[test] fn test_parse_message_id() { assert_eq!(