From 47500bb3ea58e6a24127029f0e939a866043a789 Mon Sep 17 00:00:00 2001 From: Hocuri Date: Sun, 9 Oct 2022 17:06:31 +0200 Subject: [PATCH] Fix some things, move to new file src/authentication_results_handling.rs --- src/authentication_results_handling.rs | 341 +++++++++++++++++++++++++ src/context.rs | 6 + src/decrypt.rs | 150 +---------- src/lib.rs | 1 + src/mimeparser.rs | 124 +-------- src/sql/migrations.rs | 5 +- 6 files changed, 358 insertions(+), 269 deletions(-) create mode 100644 src/authentication_results_handling.rs diff --git a/src/authentication_results_handling.rs b/src/authentication_results_handling.rs new file mode 100644 index 000000000..b4b288c7c --- /dev/null +++ b/src/authentication_results_handling.rs @@ -0,0 +1,341 @@ +//! TODO file comment. + +use std::collections::HashMap; +use std::collections::HashSet; + +use anyhow::{Context as _, Result}; +use mailparse::MailHeaderMap; + +use crate::context::Context; +use crate::headerdef::HeaderDef; + +use crate::tools; +use crate::tools::EmailAddress; + +#[derive(Debug, PartialEq, Eq)] +pub(crate) struct AuthenticationResults { + dkim_passed: bool, +} + +pub(crate) type AuthservId = String; + +pub(crate) fn parse_authentication_results( + headers: &mailparse::headers::Headers<'_>, + from: &str, +) -> Result> { + // TODO old comment: + // TODO this doesn't work for e.g. GMX which sells @gmx.de addresses, but uses gmx.net as its server + // Config::ConfiguredProvider doesn't work for e.g. Gmail which uses mx.google.com. + // + // We could self-send a message during configure and use the Authentication-Results header from there - + // this works for e.g. GMX, but not for Testrun and GMAIL. + // -> Alternatively, we could send a message to nonexistent@example.com and wait for the NDN. This works + // for Gmail, but the Testrun NDN doesn't contain such a header, and GMX returns an error directly + // while sending. + // + // We could save this info in the provider db, but this only works for these providers. + + // let from = match from.first() { + // Some(f) => &f.addr, + // None => return Ok(HashMap::new()), + // }; // TODO + let sender_domain = crate::tools::EmailAddress::new(from)?.domain; + + let mut header_map: HashMap> = HashMap::new(); + for header_value in headers.get_all_values(HeaderDef::AuthenticationResults.into()) { + // TODO there could be a comment [CFWS] before the self domain. Do we care? Probably not. + let authserv_id = header_value + .split_whitespace() + .next() + .context("Empty header")?; // TODO do we really want to return Err here if it's empty + header_map + .entry(authserv_id.to_string()) + .or_default() + .push(header_value); + } + + let mut authresults_map = HashMap::new(); + for (authserv_id, headers) in header_map { + let dkim_passed = authresults_dkim_passed(&headers, &sender_domain)?; + authresults_map.insert(authserv_id, AuthenticationResults { dkim_passed }); + } + + Ok(authresults_map) +} + +/// Parses the Authentication-Results headers belonging to a specific authserv-id +/// and returns whether they say that DKIM passed. +/// TODO document better +/// TODO if there are multiple headers and one says `pass`, one says `fail`, `none` +/// or whatever, then we still interpret that as `pass` - is this a problem? +fn authresults_dkim_passed(headers: &[String], sender_domain: &str) -> Result { + for header_value in headers { + if let Some((_start, dkim_to_end)) = header_value.split_once("dkim=") { + let dkim_part = dkim_to_end + .split(';') + .next() + .context("what the hell TODO")?; + let dkim_parts: Vec<_> = dkim_part.split_whitespace().collect(); + if let Some(&"pass") = dkim_parts.first() { + let header_d: &str = &format!("header.d={}", &sender_domain); + let header_i: &str = &format!("header.i=@{}", &sender_domain); + + if dkim_parts.contains(&header_d) || dkim_parts.contains(&header_i) { + // We have found a `dkim=pass` header! + return Ok(true); + } + } + } + } + + Ok(false) +} + +// TODO this is only half of the algorithm we thought of; we also wanted to save how sure we are +// about the authserv id. Like, a same-domain email is more trustworthy. +pub(crate) async fn update_authservid_candidates( + context: &Context, + authentication_results: &HashMap, +) -> Result<()> { + let mut new_ids: HashSet<_> = authentication_results.keys().map(String::as_str).collect(); + if new_ids.is_empty() { + // The incoming message doesn't contain any authentication results, maybe it's a + // self-sent or a mailer-daemon message + return Ok(()); + } + + let ids_config; + if let Some(ids_config_temp) = context + .get_config(crate::config::Config::AuthservIdCandidates) + .await? + { + ids_config = ids_config_temp; + let old_ids: HashSet<_> = ids_config.split(' ').collect(); + if !old_ids.is_empty() { + new_ids = old_ids.intersection(&new_ids).copied().collect(); + } + } + // If there were no AuthservIdCandidates previously, just start with + // the ones from the incoming email + + let new_config = new_ids.into_iter().collect::>().join(" "); + context + .set_config( + crate::config::Config::AuthservIdCandidates, + Some(&new_config), + ) + .await?; + + Ok(()) +} + +/// We disallow changes to the autocrypt key if DKIM failed, but worked in the past, +/// because we then assume that the From header is forged. +pub(crate) async fn should_allow_keychange( + context: &Context, + authentication_results: &HashMap, + from: &str, +) -> Result { + // TODO code duplication with update_authservid_candidates() + let mut dkim_passed = true; // TODO what do we want to do if there are multiple or no authservid candidates? + if let Some(ids_config) = context + .get_config(crate::config::Config::AuthservIdCandidates) + .await? + { + let ids = ids_config.split(' ').filter(|s| !s.is_empty()); + dbg!(&ids_config); + if let Some(authserv_id) = tools::single_value(ids) { + // dbg!(&authentication_results, &ids_config); + // TODO unwrap + dkim_passed = authentication_results.get(authserv_id).unwrap().dkim_passed; + } + } + + let sending_domain = from.parse::().unwrap().domain; // TODO unwrap + let dkim_known_to_work = context + .sql + .query_get_value( + "SELECT correct_dkim FROM sending_domains WHERE domain=?;", + paramsv![sending_domain], + ) + .await? + .unwrap_or(false); + + if !dkim_known_to_work && dkim_passed { + context + .sql + .execute( + "UPDATE sending_domains SET correct_dkim=1 WHERE domain=?;", + paramsv![sending_domain], + ) + .await?; + } + + Ok(dkim_passed || !dkim_known_to_work) +} + +#[cfg(test)] +mod tests { + use tokio::fs; + use tokio::io::AsyncReadExt; + + use super::*; + use crate::headerdef::HeaderDefMap; + use crate::test_utils::*; + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_parse_authentication_results() -> Result<()> { + let t = TestContext::new().await; + t.configure_addr("alice@gmx.net").await; + let bytes = b"From: info@slack.com +Authentication-Results: gmx.net; dkim=pass header.i=@slack.com +Authentication-Results: gmx.net; dkim=pass header.i=@amazonses.com"; + let mail = mailparse::parse_mail(bytes)?; + let actual = parse_authentication_results(&mail.get_headers(), "info@slack.com").unwrap(); + assert_eq!( + actual, + [( + "gmx.net".to_string(), + AuthenticationResults { dkim_passed: true } + )] + .into() + ); + + // TODO test that foreign Auth-Res headers are ignored + + // check_parse_authentication_results_combination( + // "alice@testrun.org", + // // TODO actually the address is alice@gmx.de, but then it doesn't work because `header.d=gmx.net`: + // b"From: alice@gmx.net + // Authentication-Results: testrun.org; + // dkim=pass header.d=gmx.net header.s=badeba3b8450 header.b=Gug6p4zD; + // dmarc=pass (policy=none) header.from=gmx.de; + // spf=pass (testrun.org: domain of alice@gmx.de designates 212.227.17.21 as permitted sender) smtp.mailfrom=alice@gmx.de", + // AuthenticationResults::Passed, + // ) + // .await; + + // check_parse_authentication_results_combination( + // "alice@testrun.org", + // br#"From: hocuri@testrun.org + // Authentication-Results: box.hispanilandia.net; dmarc=none (p=none dis=none) header.from=nauta.cu + // Authentication-Results: box.hispanilandia.net; spf=pass smtp.mailfrom=adbenitez@nauta.cu + // Authentication-Results: testrun.org; + // dkim=fail ("body hash did not verify") header.d=nauta.cu header.s=nauta header.b=YrWhU6qk; + // dmarc=none; + // spf=pass (testrun.org: domain of "test1-bounces+hocuri=testrun.org@hispanilandia.net" designates 51.15.127.36 as permitted sender) smtp.mailfrom="test1-bounces+hocuri=testrun.org@hispanilandia.net" + // "#, + // AuthenticationResults::Failed, + // ) + // .await; + + // check_parse_authentication_results_combination( + + // // TODO fails because mx.google.com, not google.com + // "alice@gmail.com", + // br#"From: not-so-fake@hispanilandia.net + // Authentication-Results: mx.google.com; + // dkim=pass header.i=@hispanilandia.net header.s=mail header.b="Ih5Sz2/P"; + // spf=pass (google.com: domain of not-so-fake@hispanilandia.net designates 51.15.127.36 as permitted sender) smtp.mailfrom=not-so-fake@hispanilandia.net; + // dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=hispanilandia.net"#, + // AuthenticationResults::Passed, + // ) + // .await; + + // check_parse_authentication_results_combination( + // "alice@nauta.cu", + // br#"From: adb + // Authentication-Results: box.hispanilandia.net; + // dkim=fail reason="signature verification failed" (2048-bit key; secure) header.d=disroot.org header.i=@disroot.org header.b="kqh3WUKq"; + // dkim-atps=neutral + // Authentication-Results: box.hispanilandia.net; dmarc=pass (p=quarantine dis=none) header.from=disroot.org + // Authentication-Results: box.hispanilandia.net; spf=pass smtp.mailfrom=adbenitez@disroot.org"#, + // AuthenticationResults::Passed, + // ) + // .await; + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_realworld_authentication_results() -> Result<()> { + let mut dir = fs::read_dir("test-data/message/dkimchecks-2022-09-28/") + .await + .unwrap(); + let mut bytes = Vec::new(); + while let Some(entry) = dir.next_entry().await.unwrap() { + let self_addr = entry.file_name().into_string().unwrap(); + let mut dir = fs::read_dir(entry.path()).await.unwrap(); + + let t = TestContext::new().await; + t.configure_addr(&self_addr).await; + + while let Some(entry) = dir.next_entry().await.unwrap() { + let mut file = fs::File::open(entry.path()).await?; + println!("{:?}", entry.path()); + bytes.clear(); + file.read_to_end(&mut bytes).await.unwrap(); + if bytes.is_empty() { + continue; + } + + let mail = mailparse::parse_mail(&bytes)?; + // TODO code duplication with create_decryption_info() + let from = mail + .headers + .get_header(HeaderDef::From_) + .and_then(|from_addr| mailparse::addrparse_header(from_addr).ok()) + .and_then(|from| from.extract_single_info()) + .map(|from| from.addr) + .unwrap_or_default(); + + // TODO code duplication with create_decryption_info() + let authentication_results = + parse_authentication_results(&mail.get_headers(), &from)?; + update_authservid_candidates(&t, &authentication_results).await?; + let allow_keychange = + should_allow_keychange(&t, &authentication_results, &from).await?; + + assert!(allow_keychange); + + // check_parse_authentication_results_combination( + // &self_addr, + // &bytes, + // AuthenticationResults::Passed, + // ) + // .await; + } + + std::mem::forget(t) // TODO dbg + } + Ok(()) + } + + // async fn check_parse_authentication_results_combination( + // self_addr: &str, + // header_bytes: &[u8], + // expected_result: AuthenticationResults, + // ) { + // let t = TestContext::new().await; + // t.set_primary_self_addr(self_addr).await.unwrap(); + // let mail = mailparse::parse_mail(body)?; + + // let actual = parse_authentication_results(&t, &mail.get_headers(), &from)?; + // //assert_eq!(message.authentication_results, expected_result); + // if message.authentication_results != expected_result { + // eprintln!( + // "EXPECTED {expected_result:?}, GOT {:?}, SELF {}, FROM {:?}", + // message.authentication_results, + // self_addr, + // message.from.first().map(|i| &i.addr), + // ) + // } else { + // eprintln!( + // "CORRECT {:?}, SELF {}, FROM {:?}", + // message.authentication_results, + // self_addr, + // message.from.first().map(|i| &i.addr), + // ) + // } + // } +} diff --git a/src/context.rs b/src/context.rs index 520afaea7..285e7dd77 100644 --- a/src/context.rs +++ b/src/context.rs @@ -544,6 +544,12 @@ impl Context { .await? .to_string(), ); + res.insert( + "authserv_id_candidates", + self.get_config(Config::AuthservIdCandidates) + .await? + .unwrap_or_default(), + ); let elapsed = self.creation_time.elapsed(); res.insert("uptime", duration_to_str(elapsed.unwrap_or_default())); diff --git a/src/decrypt.rs b/src/decrypt.rs index 81f36c7b3..b28b69d18 100644 --- a/src/decrypt.rs +++ b/src/decrypt.rs @@ -1,13 +1,15 @@ //! End-to-end decryption support. -use std::collections::HashMap; use std::collections::HashSet; use anyhow::{Context as _, Result}; -use mailparse::MailHeaderMap; + use mailparse::ParsedMail; use crate::aheader::Aheader; +use crate::authentication_results_handling::parse_authentication_results; +use crate::authentication_results_handling::should_allow_keychange; +use crate::authentication_results_handling::update_authservid_candidates; use crate::contact::addr_cmp; use crate::context::Context; use crate::headerdef::HeaderDef; @@ -17,7 +19,6 @@ use crate::keyring::Keyring; use crate::log::LogExt; use crate::peerstate::Peerstate; use crate::pgp; -use crate::tools; /// Tries to decrypt a message, but only if it is structured as an /// Autocrypt message. @@ -58,126 +59,6 @@ pub async fn try_decrypt( .await } -// TODO move somewhere else - -#[derive(Debug)] -struct AuthenticationResults { - dkim_passed: bool, -} - -type AuthservId = String; - -fn parse_authentication_results( - context: &Context, - headers: &mailparse::headers::Headers<'_>, - from: &str, -) -> Result> { - // TODO old comment: - // TODO this doesn't work for e.g. GMX which sells @gmx.de addresses, but uses gmx.net as its server - // Config::ConfiguredProvider doesn't work for e.g. Gmail which uses mx.google.com. - // - // We could self-send a message during configure and use the Authentication-Results header from there - - // this works for e.g. GMX, but not for Testrun and GMAIL. - // -> Alternatively, we could send a message to nonexistent@example.com and wait for the NDN. This works - // for Gmail, but the Testrun NDN doesn't contain such a header, and GMX returns an error directly - // while sending. - // - // We could save this info in the provider db, but this only works for these providers. - - // let from = match from.first() { - // Some(f) => &f.addr, - // None => return Ok(HashMap::new()), - // }; // TODO - let sender_domain = crate::tools::EmailAddress::new(from)?.domain; - - let mut header_map: HashMap> = HashMap::new(); - for header_value in headers.get_all_values(HeaderDef::AuthenticationResults.into()) { - // TODO there could be a comment [CFWS] before the self domain. Do we care? Probably not. - let authserv_id = header_value - .split_whitespace() - .next() - .context("Empty header")?; // TODO do we really want to return Err here if it's empty - header_map - .entry(authserv_id.to_string()) - .or_default() - .push(header_value); - } - - let mut authresults_map = HashMap::new(); - for (authserv_id, headers) in header_map { - let dkim_passed = authresults_dkim_passed(&headers, &sender_domain)?; - authresults_map.insert(authserv_id, AuthenticationResults { dkim_passed }); - } - - Ok(authresults_map) -} - -/// Parses the Authentication-Results headers belonging to a specific authserv-id -/// and returns whether they say that DKIM passed. -/// TODO document better -/// TODO if there are multiple headers and one says `pass`, one says `fail`, `none` -/// or whatever, then we still interpret that as `pass` - is this a problem? -fn authresults_dkim_passed(headers: &[String], sender_domain: &str) -> Result { - for header_value in headers { - if let Some((_start, dkim_to_end)) = header_value.split_once("dkim=") { - let dkim_part = dkim_to_end - .split(';') - .next() - .context("what the hell TODO")?; - let dkim_parts: Vec<_> = dkim_part.split_whitespace().collect(); - if let Some(&"pass") = dkim_parts.first() { - let header_d: &str = &format!("header.d={}", &sender_domain); - let header_i: &str = &format!("header.i=@{}", &sender_domain); - - if dkim_parts.contains(&header_d) || dkim_parts.contains(&header_i) { - // We have found a `dkim=pass` header! - return Ok(true); - } - } - } - } - - Ok(false) -} - -// TODO this is only half of the algorithm we thought of; we also wanted to save how sure we are -// about the authserv id. Like, a same-domain email is more trustworthy. -async fn update_authservid_candidates( - context: &Context, - authentication_results: &HashMap, -) -> Result<()> { - let mut new_ids: HashSet<_> = authentication_results.keys().map(String::as_str).collect(); - if new_ids.is_empty() { - // The incoming message doesn't contain any authentication results, maybe it's a - // self-sent or a mailer-daemon message - return Ok(()); - } - - let ids_config; - if let Some(ids_config_temp) = context - .get_config(crate::config::Config::AuthservIdCandidates) - .await? - { - ids_config = ids_config_temp; - let old_ids: HashSet<_> = ids_config.split(' ').collect(); - if !old_ids.is_empty() { - new_ids = old_ids.intersection(&new_ids).copied().collect(); - } - } - // If there were no AuthservIdCandidates previously, just start with - // the ones from the incoming email - - let new_config = new_ids.into_iter().collect::>().join(" "); - context - .set_config( - crate::config::Config::AuthservIdCandidates, - Some(&new_config), - ) - .await?; - - Ok(()) -} - pub async fn create_decryption_info( context: &Context, mail: &ParsedMail<'_>, @@ -195,33 +76,16 @@ pub async fn create_decryption_info( .ok_or_log_msg(context, "Failed to parse Autocrypt header") .flatten(); - let authentication_results = parse_authentication_results(context, &mail.get_headers(), &from)?; + let authentication_results = parse_authentication_results(&mail.get_headers(), &from)?; update_authservid_candidates(context, &authentication_results).await?; - // TODO code duplication with update_authservid_candidates() - // TODO too much low-level code - let mut dkim_passed = true; // TODO what do we want to do if there are multiple or no authservid candidates? - if let Some(ids_config) = context - .get_config(crate::config::Config::AuthservIdCandidates) - .await? - { - let ids: HashSet<_> = ids_config.split(' ').collect(); - if let Some(authserv_id) = tools::single_value(ids) { - // TODO unwrap - dkim_passed = authentication_results.get(authserv_id).unwrap().dkim_passed; - } - } - - // TODO old comment Allow changes to the autocrypt key if DKIM passed. - // If DKIM failed, we assume that the From address may have been forged - // and therefore we prohibit changes to the autocrypt key. + let allow_keychange = should_allow_keychange(context, &authentication_results, &from).await?; let peerstate = get_autocrypt_peerstate( context, &from, autocrypt_header.as_ref(), message_time, - true, // TODO key changes should not be allowed if the sending domain sent DKIM-valid emails - // until now, but this one is DKIM-invalid. + allow_keychange, ) .await?; diff --git a/src/lib.rs b/src/lib.rs index 1b510068a..a779f2ad6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,6 +93,7 @@ mod update_helper; pub mod webxdc; #[macro_use] mod dehtml; +mod authentication_results_handling; mod color; pub mod html; pub mod plaintext; diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 825bc6a91..3d31e915d 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -7,7 +7,7 @@ use std::pin::Pin; use anyhow::{bail, Context as _, Result}; use deltachat_derive::{FromSql, ToSql}; use lettre_email::mime::{self, Mime}; -use mailparse::headers::Headers; + use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo}; use once_cell::sync::Lazy; @@ -29,7 +29,7 @@ use crate::peerstate::Peerstate; use crate::simplify::{simplify, SimplifiedText}; use crate::stock_str; use crate::sync::SyncItems; -use crate::tools::{get_filemeta, parse_receive_headers, truncate_by_lines, EmailAddress}; +use crate::tools::{get_filemeta, parse_receive_headers, truncate_by_lines}; /// A parsed MIME message. /// @@ -3333,124 +3333,4 @@ Message. Ok(()) } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_parse_authentication_results() -> Result<()> { - // TODO test that foreign Auth-Res headers are ignored - check_parse_authentication_results_combination( - "alice@gmx.net", - b"From: info@slack.com -Authentication-Results: gmx.net; dkim=pass header.i=@slack.com -Authentication-Results: gmx.net; dkim=pass header.i=@amazonses.com", - AuthenticationResults::Passed, - ) - .await; - - check_parse_authentication_results_combination( - "alice@testrun.org", - // TODO actually the address is alice@gmx.de, but then it doesn't work because `header.d=gmx.net`: - b"From: alice@gmx.net -Authentication-Results: testrun.org; - dkim=pass header.d=gmx.net header.s=badeba3b8450 header.b=Gug6p4zD; - dmarc=pass (policy=none) header.from=gmx.de; - spf=pass (testrun.org: domain of alice@gmx.de designates 212.227.17.21 as permitted sender) smtp.mailfrom=alice@gmx.de", - AuthenticationResults::Passed, - ) - .await; - - check_parse_authentication_results_combination( - "alice@testrun.org", - br#"From: hocuri@testrun.org -Authentication-Results: box.hispanilandia.net; dmarc=none (p=none dis=none) header.from=nauta.cu -Authentication-Results: box.hispanilandia.net; spf=pass smtp.mailfrom=adbenitez@nauta.cu -Authentication-Results: testrun.org; - dkim=fail ("body hash did not verify") header.d=nauta.cu header.s=nauta header.b=YrWhU6qk; - dmarc=none; - spf=pass (testrun.org: domain of "test1-bounces+hocuri=testrun.org@hispanilandia.net" designates 51.15.127.36 as permitted sender) smtp.mailfrom="test1-bounces+hocuri=testrun.org@hispanilandia.net" -"#, - AuthenticationResults::Failed, - ) - .await; - - check_parse_authentication_results_combination( - - // TODO fails because mx.google.com, not google.com - "alice@gmail.com", - br#"From: not-so-fake@hispanilandia.net -Authentication-Results: mx.google.com; - dkim=pass header.i=@hispanilandia.net header.s=mail header.b="Ih5Sz2/P"; - spf=pass (google.com: domain of not-so-fake@hispanilandia.net designates 51.15.127.36 as permitted sender) smtp.mailfrom=not-so-fake@hispanilandia.net; - dmarc=pass (p=QUARANTINE sp=QUARANTINE dis=NONE) header.from=hispanilandia.net"#, - AuthenticationResults::Passed, - ) - .await; - - check_parse_authentication_results_combination( - "alice@nauta.cu", - br#"From: adb -Authentication-Results: box.hispanilandia.net; - dkim=fail reason="signature verification failed" (2048-bit key; secure) header.d=disroot.org header.i=@disroot.org header.b="kqh3WUKq"; - dkim-atps=neutral -Authentication-Results: box.hispanilandia.net; dmarc=pass (p=quarantine dis=none) header.from=disroot.org -Authentication-Results: box.hispanilandia.net; spf=pass smtp.mailfrom=adbenitez@disroot.org"#, - AuthenticationResults::Passed, - ) - .await; - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_realworld_authentication_results() -> Result<()> { - let mut dir = fs::read_dir("test-data/message/dkimchecks-2022-09-28/") - .await - .unwrap(); - let mut bytes = Vec::new(); - while let Some(entry) = dir.next_entry().await.unwrap() { - let self_addr = entry.file_name().into_string().unwrap(); - let mut dir = fs::read_dir(entry.path()).await.unwrap(); - while let Some(entry) = dir.next_entry().await.unwrap() { - let mut file = fs::File::open(entry.path()).await?; - println!("{:?}", entry.path()); - bytes.clear(); - file.read_to_end(&mut bytes).await.unwrap(); - if bytes.is_empty() { - continue; - } - check_parse_authentication_results_combination( - &self_addr, - &bytes, - AuthenticationResults::Passed, - ) - .await; - } - } - Ok(()) - } - - async fn check_parse_authentication_results_combination( - self_addr: &str, - header_bytes: &[u8], - expected_result: AuthenticationResults, - ) { - let t = TestContext::new().await; - t.set_primary_self_addr(self_addr).await.unwrap(); - let message = MimeMessage::from_bytes(&t, header_bytes).await.unwrap(); - //assert_eq!(message.authentication_results, expected_result); - if message.authentication_results != expected_result { - eprintln!( - "EXPECTED {expected_result:?}, GOT {:?}, SELF {}, FROM {:?}", - message.authentication_results, - self_addr, - message.from.first().map(|i| &i.addr), - ) - } else { - eprintln!( - "CORRECT {:?}, SELF {}, FROM {:?}", - message.authentication_results, - self_addr, - message.from.first().map(|i| &i.addr), - ) - } - } } diff --git a/src/sql/migrations.rs b/src/sql/migrations.rs index e6e1a1d4d..4103ca08d 100644 --- a/src/sql/migrations.rs +++ b/src/sql/migrations.rs @@ -599,10 +599,7 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid); if dbversion < 92 { info!(context, "[migration] v92"); sql.execute_migration( - // TODO Is this really the database scheme we want? - // Would be possible to save the timestamp here until when it was correct (change it as soon as it becomes incorrect). - // Then if this is old enough, accept a deviating key again - "ALTER TABLE acpeerstates ADD COLUMN dkim_status INTEGER DEFAULT 0;", + "CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, correct_dkim INTEGER DEFAULT 0);", 92, ) .await?;