mirror of
https://github.com/chatmail/core.git
synced 2026-04-26 09:56:35 +03:00
Check DKIM Authentication-Results (#3583)
Fix #3507 Note that this is not intended for a release at this point! We first have to test whether it runs stable enough. If we want to make a release while we are not confident enough in authres-checking, then we have to disable it. BTW, most of the 3000 new lines are in `test_data/messages/dkimchecks...`, not the actual code da3a4b94 adds the results to the Message info. It currently does this by adding them to `hop_info`. Maybe we should rename `hop_info` to `extra_info` or something; this has the disadvantage that we can't rename the sql column name though. Follow-ups for this could be: - In `update_authservid_candidates()`: Implement the rest of the algorithm @hpk42 and me thought about. What's missing is remembering how sure we are that these are the right authserv-ids. Esp., when receiving a message sent from another account at the same domain, we can be quite sure that the authserv-ids in there are the ones of our email server. This will make authres-checking work with buzon.uy, disroot.org, yandex.ru, mailo.com, and riseup.net. - Think about how we present this to the user - e.g. currently the only change is that we don't accept key changes, which will mean that the small lock on the message is not shown. - And it will mean that we can fully enable AEAP, after revisiting the security implications of this, and assuming everyone (esp. @link2xt who pointed out the problems in the first place) feels comfortable with it.
This commit is contained in:
@@ -66,6 +66,9 @@
|
||||
|
||||
|
||||
### Changes
|
||||
- Look at Authentication-Results. Don't accept Autocrypt key changes
|
||||
if they come with negative authentiation results while this contact
|
||||
sent emails with positive authentication results in the past. #3583
|
||||
- jsonrpc in cffi also sends events now #3662
|
||||
- jsonrpc: new format for events and better typescript autocompletion
|
||||
- Join all "[migration] vXX" log messages into one
|
||||
|
||||
744
src/authres.rs
Normal file
744
src/authres.rs
Normal file
@@ -0,0 +1,744 @@
|
||||
//! Parsing and handling of the Authentication-Results header.
|
||||
//! See the comment on [`handle_authres`] for more.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeSet;
|
||||
use std::fmt;
|
||||
|
||||
use anyhow::Result;
|
||||
use mailparse::MailHeaderMap;
|
||||
use mailparse::ParsedMail;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::context::Context;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::tools::time;
|
||||
use crate::tools::EmailAddress;
|
||||
|
||||
/// `authres` is short for the Authentication-Results header, defined in
|
||||
/// <https://datatracker.ietf.org/doc/html/rfc8601>, which contains info
|
||||
/// about whether DKIM and SPF passed.
|
||||
///
|
||||
/// To mitigate From forgery, we remember for each sending domain whether it is known
|
||||
/// to have valid DKIM. If an email from such a domain comes with invalid DKIM,
|
||||
/// we don't allow changing the autocrypt key.
|
||||
///
|
||||
/// See <https://github.com/deltachat/deltachat-core-rust/issues/3507>.
|
||||
pub(crate) async fn handle_authres(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'_>,
|
||||
from: &str,
|
||||
message_time: i64,
|
||||
) -> Result<DkimResults> {
|
||||
let from_domain = match EmailAddress::new(from) {
|
||||
Ok(email) => email.domain,
|
||||
Err(e) => {
|
||||
warn!(context, "invalid email {:#}", e);
|
||||
// This email is invalid, but don't return an error, we still want to
|
||||
// add a stub to the database so that it's not downloaded again
|
||||
return Ok(DkimResults::default());
|
||||
}
|
||||
};
|
||||
|
||||
let authres = parse_authres_headers(&mail.get_headers(), &from_domain);
|
||||
update_authservid_candidates(context, &authres).await?;
|
||||
compute_dkim_results(context, authres, &from_domain, message_time).await
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct DkimResults {
|
||||
/// Whether DKIM passed for this particular e-mail.
|
||||
pub dkim_passed: bool,
|
||||
/// Whether DKIM is known to work for e-mails coming from the sender's domain,
|
||||
/// i.e. whether we expect DKIM to work.
|
||||
pub dkim_should_work: bool,
|
||||
/// Whether changing the public Autocrypt key should be allowed.
|
||||
/// This is false if we expected DKIM to work (dkim_works=true),
|
||||
/// but it failed now (dkim_passed=false).
|
||||
pub allow_keychange: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for DkimResults {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
fmt,
|
||||
"DKIM Results: Passed={}, Works={}, Allow_Keychange={}",
|
||||
self.dkim_passed, self.dkim_should_work, self.allow_keychange
|
||||
)?;
|
||||
if !self.allow_keychange {
|
||||
write!(fmt, " KEYCHANGES NOT ALLOWED!!!!")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
type AuthservId = String;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum DkimResult {
|
||||
/// The header explicitly said that DKIM passed
|
||||
Passed,
|
||||
/// The header explicitly said that DKIM failed
|
||||
Failed,
|
||||
/// The header didn't say anything about DKIM; this might mean that it wasn't
|
||||
/// checked, but it might also mean that it failed. This is because some providers
|
||||
/// (e.g. ik.me, mail.ru, posteo.de) don't add `dkim=none` to their
|
||||
/// Authentication-Results if there was no DKIM.
|
||||
Nothing,
|
||||
}
|
||||
|
||||
type ParsedAuthresHeaders = Vec<(AuthservId, DkimResult)>;
|
||||
|
||||
fn parse_authres_headers(
|
||||
headers: &mailparse::headers::Headers<'_>,
|
||||
from_domain: &str,
|
||||
) -> ParsedAuthresHeaders {
|
||||
let mut res = Vec::new();
|
||||
for header_value in headers.get_all_values(HeaderDef::AuthenticationResults.into()) {
|
||||
let header_value = remove_comments(&header_value);
|
||||
|
||||
if let Some(mut authserv_id) = header_value.split(';').next() {
|
||||
if authserv_id.contains(char::is_whitespace) || authserv_id.is_empty() {
|
||||
// Outlook violates the RFC by not adding an authserv-id at all, which we notice
|
||||
// because there is whitespace in the first identifier before the ';'.
|
||||
// Authentication-Results-parsing still works securely because they remove incoming
|
||||
// Authentication-Results headers.
|
||||
// We just use an arbitrary authserv-id, it will work for Outlook, and in general,
|
||||
// with providers not implementing the RFC correctly, someone can trick us
|
||||
// into thinking that an incoming email is DKIM-correct, anyway.
|
||||
// The most important thing here is that we have some valid `authserv_id`.
|
||||
authserv_id = "invalidAuthservId";
|
||||
}
|
||||
let dkim_passed = parse_one_authres_header(&header_value, from_domain);
|
||||
res.push((authserv_id.to_string(), dkim_passed));
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
/// The headers can contain comments that look like this:
|
||||
/// ```text
|
||||
/// Authentication-Results: (this is a comment) gmx.net; (another; comment) dkim=pass;
|
||||
/// ```
|
||||
fn remove_comments(header: &str) -> Cow<'_, str> {
|
||||
// In Pomsky, this is:
|
||||
// "(" Codepoint* lazy ")"
|
||||
// See https://playground.pomsky-lang.org/?text=%22(%22%20Codepoint*%20lazy%20%22)%22
|
||||
static RE: Lazy<regex::Regex> = Lazy::new(|| regex::Regex::new(r"\([\s\S]*?\)").unwrap());
|
||||
|
||||
RE.replace_all(header, " ")
|
||||
}
|
||||
|
||||
/// Parses a single Authentication-Results header, like:
|
||||
///
|
||||
/// ```text
|
||||
/// Authentication-Results: gmx.net; dkim=pass header.i=@slack.com
|
||||
/// ```
|
||||
fn parse_one_authres_header(header_value: &str, from_domain: &str) -> DkimResult {
|
||||
if let Some((before_dkim_part, dkim_to_end)) = header_value.split_once("dkim=") {
|
||||
// Check that the character right before `dkim=` is a space or a tab
|
||||
// so that we wouldn't e.g. mistake `notdkim=pass` for `dkim=pass`
|
||||
if before_dkim_part.ends_with(' ') || before_dkim_part.ends_with('\t') {
|
||||
let dkim_part = dkim_to_end.split(';').next().unwrap_or_default();
|
||||
let dkim_parts: Vec<_> = dkim_part.split_whitespace().collect();
|
||||
if let Some(&"pass") = dkim_parts.first() {
|
||||
// DKIM headers contain a header.d or header.i field
|
||||
// that says which domain signed. We have to check ourselves
|
||||
// that this is the same domain as in the From header.
|
||||
let header_d: &str = &format!("header.d={}", &from_domain);
|
||||
let header_i: &str = &format!("header.i=@{}", &from_domain);
|
||||
|
||||
if dkim_parts.contains(&header_d) || dkim_parts.contains(&header_i) {
|
||||
// We have found a `dkim=pass` header!
|
||||
return DkimResult::Passed;
|
||||
}
|
||||
} else {
|
||||
// dkim=fail, dkim=none, ...
|
||||
return DkimResult::Failed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DkimResult::Nothing
|
||||
}
|
||||
|
||||
/// ## About authserv-ids
|
||||
///
|
||||
/// After having checked DKIM, our email server adds an Authentication-Results header.
|
||||
///
|
||||
/// Now, an attacker could just add an Authentication-Results header that says dkim=pass
|
||||
/// in order to make us think that DKIM was correct in their From-forged email.
|
||||
///
|
||||
/// In order to prevent this, each email server adds its authserv-id to the
|
||||
/// Authentication-Results header, e.g. Testrun's authserv-id is `testrun.org`, Gmail's
|
||||
/// is `mx.google.com`. When Testrun gets a mail delivered from outside, it will then
|
||||
/// remove any Authentication-Results headers whose authserv-id is also `testrun.org`.
|
||||
///
|
||||
/// We need to somehow find out the authserv-id(s) of our email server, so that
|
||||
/// we can use the Authentication-Results with the right authserv-id.
|
||||
///
|
||||
/// ## What this function does
|
||||
///
|
||||
/// When receiving an email, this function is called and updates the candidates for
|
||||
/// our server's authserv-id, i.e. what we think our server's authserv-id is.
|
||||
///
|
||||
/// Usually, every incoming email has Authentication-Results with our server's
|
||||
/// authserv-id, so, the intersection of the existing authserv-ids and the incoming
|
||||
/// authserv-ids for our server's authserv-id is a good guess for our server's
|
||||
/// authserv-id. When this intersection is empty, we assume that the authserv-id has
|
||||
/// changed and start over with the new authserv-ids.
|
||||
///
|
||||
/// See [`handle_authres`].
|
||||
async fn update_authservid_candidates(
|
||||
context: &Context,
|
||||
authres: &ParsedAuthresHeaders,
|
||||
) -> Result<()> {
|
||||
let mut new_ids: BTreeSet<&str> = authres
|
||||
.iter()
|
||||
.map(|(authserv_id, _dkim_passed)| authserv_id.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 old_config = context.get_config(Config::AuthservIdCandidates).await?;
|
||||
let old_ids = parse_authservid_candidates_config(&old_config);
|
||||
let intersection: BTreeSet<&str> = old_ids.intersection(&new_ids).copied().collect();
|
||||
if !intersection.is_empty() {
|
||||
new_ids = intersection;
|
||||
}
|
||||
// If there were no AuthservIdCandidates previously, just start with
|
||||
// the ones from the incoming email
|
||||
|
||||
if old_ids != new_ids {
|
||||
let new_config = new_ids.into_iter().collect::<Vec<_>>().join(" ");
|
||||
context
|
||||
.set_config(Config::AuthservIdCandidates, Some(&new_config))
|
||||
.await?;
|
||||
// Updating the authservid candidates may mean that we now consider
|
||||
// emails as "failed" which "passed" previously, so we need to
|
||||
// reset our expectation which DKIMs work.
|
||||
clear_dkim_works(context).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Use the parsed authres and the authservid candidates to compute whether DKIM passed
|
||||
/// and whether a keychange should be allowed.
|
||||
///
|
||||
/// We track in the `sending_domains` table whether we get positive Authentication-Results
|
||||
/// for mails from a contact (meaning that their provider properly authenticates against
|
||||
/// our provider).
|
||||
///
|
||||
/// Once a contact is known to come with positive Authentication-Resutls (dkim: pass),
|
||||
/// we don't accept Autocrypt key changes if they come with negative Authentication-Results.
|
||||
async fn compute_dkim_results(
|
||||
context: &Context,
|
||||
mut authres: ParsedAuthresHeaders,
|
||||
from_domain: &str,
|
||||
message_time: i64,
|
||||
) -> Result<DkimResults> {
|
||||
let mut dkim_passed = false;
|
||||
|
||||
let ids_config = context.get_config(Config::AuthservIdCandidates).await?;
|
||||
let ids = parse_authservid_candidates_config(&ids_config);
|
||||
|
||||
// Remove all foreign authentication results
|
||||
authres.retain(|(authserv_id, _dkim_passed)| ids.contains(authserv_id.as_str()));
|
||||
|
||||
if authres.is_empty() {
|
||||
// If the authentication results are empty, then our provider doesn't add them
|
||||
// and an attacker could just add their own Authentication-Results, making us
|
||||
// think that DKIM passed. So, in this case, we can as well assume that DKIM passed.
|
||||
dkim_passed = true;
|
||||
} else {
|
||||
for (_authserv_id, current_dkim_passed) in authres {
|
||||
match current_dkim_passed {
|
||||
DkimResult::Passed => {
|
||||
dkim_passed = true;
|
||||
break;
|
||||
}
|
||||
DkimResult::Failed => {
|
||||
dkim_passed = false;
|
||||
break;
|
||||
}
|
||||
DkimResult::Nothing => {
|
||||
// Continue looking for an Authentication-Results header
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let last_working_timestamp = dkim_works_timestamp(context, from_domain).await?;
|
||||
let mut dkim_should_work = dkim_should_work(last_working_timestamp)?;
|
||||
if message_time > last_working_timestamp && dkim_passed {
|
||||
set_dkim_works_timestamp(context, from_domain, message_time).await?;
|
||||
dkim_should_work = true;
|
||||
}
|
||||
|
||||
Ok(DkimResults {
|
||||
dkim_passed,
|
||||
dkim_should_work,
|
||||
allow_keychange: dkim_passed || !dkim_should_work,
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether DKIM in emails from this domain should be considered to work.
|
||||
fn dkim_should_work(last_working_timestamp: i64) -> Result<bool> {
|
||||
// When we get an email with valid DKIM-Authentication-Results,
|
||||
// then we assume that DKIM works for 30 days from this time on.
|
||||
let should_work_until = last_working_timestamp + 3600 * 24 * 30;
|
||||
|
||||
let dkim_ever_worked = last_working_timestamp > 0;
|
||||
|
||||
// We're using time() here and not the time when the message
|
||||
// claims to have been sent (passed around as `message_time`)
|
||||
// because otherwise an attacker could just put a time way
|
||||
// in the future into the `Date` header and then we would
|
||||
// assume that DKIM doesn't have to be valid anymore.
|
||||
let dkim_should_work_now = should_work_until > time();
|
||||
Ok(dkim_ever_worked && dkim_should_work_now)
|
||||
}
|
||||
|
||||
async fn dkim_works_timestamp(context: &Context, from_domain: &str) -> Result<i64, anyhow::Error> {
|
||||
let last_working_timestamp: i64 = context
|
||||
.sql
|
||||
.query_get_value(
|
||||
"SELECT dkim_works FROM sending_domains WHERE domain=?",
|
||||
paramsv![from_domain],
|
||||
)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
Ok(last_working_timestamp)
|
||||
}
|
||||
|
||||
async fn set_dkim_works_timestamp(
|
||||
context: &Context,
|
||||
from_domain: &str,
|
||||
timestamp: i64,
|
||||
) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute(
|
||||
"INSERT INTO sending_domains (domain, dkim_works) VALUES (?,?)
|
||||
ON CONFLICT(domain) DO UPDATE SET dkim_works=excluded.dkim_works",
|
||||
paramsv![from_domain, timestamp],
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn clear_dkim_works(context: &Context) -> Result<()> {
|
||||
context
|
||||
.sql
|
||||
.execute("DELETE FROM sending_domains", paramsv![])
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_authservid_candidates_config(config: &Option<String>) -> BTreeSet<&str> {
|
||||
config
|
||||
.as_deref()
|
||||
.map(|c| c.split_whitespace().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::indexing_slicing)]
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::fs;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::e2ee;
|
||||
use crate::mimeparser;
|
||||
use crate::peerstate::Peerstate;
|
||||
use crate::securejoin::get_securejoin_qr;
|
||||
use crate::securejoin::join_securejoin;
|
||||
use crate::test_utils;
|
||||
use crate::test_utils::TestContext;
|
||||
use crate::test_utils::TestContextManager;
|
||||
use crate::tools;
|
||||
|
||||
#[test]
|
||||
fn test_remove_comments() {
|
||||
let header = "Authentication-Results: mx3.messagingengine.com;
|
||||
dkim=pass (1024-bit rsa key sha256) header.d=riseup.net;"
|
||||
.to_string();
|
||||
assert_eq!(
|
||||
remove_comments(&header),
|
||||
"Authentication-Results: mx3.messagingengine.com;
|
||||
dkim=pass header.d=riseup.net;"
|
||||
);
|
||||
|
||||
let header = ") aaa (".to_string();
|
||||
assert_eq!(remove_comments(&header), ") aaa (");
|
||||
|
||||
let header = "((something weird) no comment".to_string();
|
||||
assert_eq!(remove_comments(&header), " no comment");
|
||||
|
||||
let header = "🎉(🎉(🎉))🎉(".to_string();
|
||||
assert_eq!(remove_comments(&header), "🎉 )🎉(");
|
||||
|
||||
// Comments are allowed to include whitespace
|
||||
let header = "(com\n\t\r\nment) no comment (comment)".to_string();
|
||||
assert_eq!(remove_comments(&header), " no comment ");
|
||||
}
|
||||
|
||||
#[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"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_authres_headers(&mail.get_headers(), "slack.com");
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![
|
||||
("gmx.net".to_string(), DkimResult::Passed),
|
||||
("gmx.net".to_string(), DkimResult::Nothing)
|
||||
]
|
||||
);
|
||||
|
||||
let bytes = b"Authentication-Results: gmx.net; notdkim=pass header.i=@slack.com
|
||||
Authentication-Results: gmx.net; notdkim=pass header.i=@amazonses.com";
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "slack.com");
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![
|
||||
("gmx.net".to_string(), DkimResult::Nothing),
|
||||
("gmx.net".to_string(), DkimResult::Nothing)
|
||||
]
|
||||
);
|
||||
|
||||
let bytes = b"Authentication-Results: gmx.net; dkim=pass header.i=@amazonses.com";
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "slack.com");
|
||||
assert_eq!(actual, vec![("gmx.net".to_string(), DkimResult::Nothing)],);
|
||||
|
||||
// Weird Authentication-Results from Outlook without an authserv-id
|
||||
let bytes = b"Authentication-Results: spf=pass (sender IP is 40.92.73.85)
|
||||
smtp.mailfrom=hotmail.com; dkim=pass (signature was verified)
|
||||
header.d=hotmail.com;dmarc=pass action=none
|
||||
header.from=hotmail.com;compauth=pass reason=100";
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "hotmail.com");
|
||||
// At this point, the most important thing to test is that there are no
|
||||
// authserv-ids with whitespace in them.
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![("invalidAuthservId".to_string(), DkimResult::Passed)]
|
||||
);
|
||||
|
||||
let bytes = b"Authentication-Results: gmx.net; dkim=none header.i=@slack.com
|
||||
Authentication-Results: gmx.net; dkim=pass header.i=@slack.com";
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "slack.com");
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![
|
||||
("gmx.net".to_string(), DkimResult::Failed),
|
||||
("gmx.net".to_string(), DkimResult::Passed)
|
||||
]
|
||||
);
|
||||
|
||||
// ';' in comments
|
||||
let bytes = b"Authentication-Results: mx1.riseup.net;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=yandex.ru header.i=@yandex.ru header.a=rsa-sha256 header.s=mail header.b=avNJu6sw;
|
||||
dkim-atps=neutral";
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "yandex.ru");
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![("mx1.riseup.net".to_string(), DkimResult::Passed)]
|
||||
);
|
||||
|
||||
let bytes = br#"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"#;
|
||||
let mail = mailparse::parse_mail(bytes)?;
|
||||
let actual = parse_authres_headers(&mail.get_headers(), "disroot.org");
|
||||
assert_eq!(
|
||||
actual,
|
||||
vec![
|
||||
("box.hispanilandia.net".to_string(), DkimResult::Failed),
|
||||
("box.hispanilandia.net".to_string(), DkimResult::Nothing),
|
||||
("box.hispanilandia.net".to_string(), DkimResult::Nothing),
|
||||
]
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_update_authservid_candidates() -> Result<()> {
|
||||
let t = TestContext::new_alice().await;
|
||||
|
||||
update_authservid_candidates_test(&t, &["mx3.messagingengine.com"]).await;
|
||||
let candidates = t.get_config(Config::AuthservIdCandidates).await?.unwrap();
|
||||
assert_eq!(candidates, "mx3.messagingengine.com");
|
||||
|
||||
// "mx4.messagingengine.com" seems to be the new authserv-id, DC should accept it
|
||||
update_authservid_candidates_test(&t, &["mx4.messagingengine.com"]).await;
|
||||
let candidates = t.get_config(Config::AuthservIdCandidates).await?.unwrap();
|
||||
assert_eq!(candidates, "mx4.messagingengine.com");
|
||||
|
||||
// A message without any Authentication-Results headers shouldn't remove all
|
||||
// candidates since it could be a mailer-daemon message or so
|
||||
update_authservid_candidates_test(&t, &[]).await;
|
||||
let candidates = t.get_config(Config::AuthservIdCandidates).await?.unwrap();
|
||||
assert_eq!(candidates, "mx4.messagingengine.com");
|
||||
|
||||
update_authservid_candidates_test(&t, &["mx4.messagingengine.com", "someotherdomain.com"])
|
||||
.await;
|
||||
let candidates = t.get_config(Config::AuthservIdCandidates).await?.unwrap();
|
||||
assert_eq!(candidates, "mx4.messagingengine.com");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Calls update_authservid_candidates(), meant for using in a test.
|
||||
///
|
||||
/// update_authservid_candidates() only looks at the keys of its
|
||||
/// `authentication_results` parameter. So, this function takes `incoming_ids`
|
||||
/// and adds some AuthenticationResults to get the HashMap we need.
|
||||
async fn update_authservid_candidates_test(context: &Context, incoming_ids: &[&str]) {
|
||||
let v = incoming_ids
|
||||
.iter()
|
||||
.map(|id| (id.to_string(), DkimResult::Passed))
|
||||
.collect();
|
||||
update_authservid_candidates(context, &v).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_realworld_authentication_results() -> Result<()> {
|
||||
let mut test_failed = false;
|
||||
|
||||
let dir = tools::read_dir("test-data/message/dkimchecks-2022-09-28/".as_ref())
|
||||
.await
|
||||
.unwrap();
|
||||
let mut bytes = Vec::new();
|
||||
for entry in dir {
|
||||
if !entry.file_type().await.unwrap().is_dir() {
|
||||
continue;
|
||||
}
|
||||
let self_addr = entry.file_name().into_string().unwrap();
|
||||
let self_domain = EmailAddress::new(&self_addr).unwrap().domain;
|
||||
let authres_parsing_works = [
|
||||
"ik.me",
|
||||
"web.de",
|
||||
"posteo.de",
|
||||
"gmail.com",
|
||||
"hotmail.com",
|
||||
"mail.ru",
|
||||
"aol.com",
|
||||
"yahoo.com",
|
||||
"icloud.com",
|
||||
"fastmail.com",
|
||||
"mail.de",
|
||||
"outlook.com",
|
||||
"gmx.de",
|
||||
"testrun.org",
|
||||
]
|
||||
.contains(&self_domain.as_str());
|
||||
|
||||
let t = TestContext::new().await;
|
||||
t.configure_addr(&self_addr).await;
|
||||
if !authres_parsing_works {
|
||||
println!("========= Receiving as {} =========", &self_addr);
|
||||
}
|
||||
|
||||
// Simulate receiving all emails once, so that we have the correct authserv-ids
|
||||
let mut dir = tools::read_dir(&entry.path()).await.unwrap();
|
||||
|
||||
// The ordering in which the emails are received can matter;
|
||||
// the test _should_ pass for every ordering.
|
||||
dir.sort_by_key(|d| d.file_name());
|
||||
//rand::seq::SliceRandom::shuffle(&mut dir[..], &mut rand::thread_rng());
|
||||
|
||||
for entry in &dir {
|
||||
let mut file = fs::File::open(entry.path()).await?;
|
||||
bytes.clear();
|
||||
file.read_to_end(&mut bytes).await.unwrap();
|
||||
|
||||
let mail = mailparse::parse_mail(&bytes)?;
|
||||
let from = &mimeparser::get_from(&mail.headers)[0].addr;
|
||||
|
||||
let res = handle_authres(&t, &mail, from, time()).await?;
|
||||
assert!(res.allow_keychange);
|
||||
}
|
||||
|
||||
for entry in &dir {
|
||||
let mut file = fs::File::open(entry.path()).await?;
|
||||
bytes.clear();
|
||||
file.read_to_end(&mut bytes).await.unwrap();
|
||||
|
||||
let mail = mailparse::parse_mail(&bytes)?;
|
||||
let from = &mimeparser::get_from(&mail.headers)[0].addr;
|
||||
|
||||
let res = handle_authres(&t, &mail, from, time()).await?;
|
||||
if !res.allow_keychange {
|
||||
println!(
|
||||
"!!!!!! FAILURE Receiving {:?}, keychange is not allowed !!!!!!",
|
||||
entry.path()
|
||||
);
|
||||
test_failed = true;
|
||||
}
|
||||
|
||||
let from_domain = EmailAddress::new(from).unwrap().domain;
|
||||
assert_eq!(
|
||||
res.dkim_should_work,
|
||||
dkim_should_work(dkim_works_timestamp(&t, &from_domain).await?)?
|
||||
);
|
||||
assert_eq!(res.dkim_passed, res.dkim_should_work);
|
||||
|
||||
// delta.blinzeln.de and gmx.de have invalid DKIM, so the DKIM check should fail
|
||||
let expected_result = (from_domain != "delta.blinzeln.de") && (from_domain != "gmx.de")
|
||||
// These are (fictional) forged emails where the attacker added a fake
|
||||
// Authentication-Results before sending the email
|
||||
&& from != "forged-authres-added@example.com"
|
||||
// Other forged emails
|
||||
&& !from.starts_with("forged");
|
||||
|
||||
if res.dkim_passed != expected_result {
|
||||
if authres_parsing_works {
|
||||
println!(
|
||||
"!!!!!! FAILURE Receiving {:?}, order {:#?} wrong result: !!!!!!",
|
||||
entry.path(),
|
||||
dir.iter().map(|e| e.file_name()).collect::<Vec<_>>()
|
||||
);
|
||||
test_failed = true;
|
||||
}
|
||||
println!("From {}: {}", from_domain, res.dkim_passed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert!(!test_failed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_handle_authres() {
|
||||
let t = TestContext::new().await;
|
||||
|
||||
// Even if the format is wrong and parsing fails, handle_authres() shouldn't
|
||||
// return an Err because this would prevent the message from being added
|
||||
// to the database and downloaded again and again
|
||||
let bytes = b"Authentication-Results: dkim=";
|
||||
let mail = mailparse::parse_mail(bytes).unwrap();
|
||||
handle_authres(&t, &mail, "invalidfrom.com", time())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn test_handle_authres_fails() -> Result<()> {
|
||||
let mut tcm = TestContextManager::new();
|
||||
let alice = tcm.alice().await;
|
||||
let bob = tcm.bob().await;
|
||||
|
||||
// Bob sends Alice a message, so she gets his key
|
||||
tcm.send_recv_accept(&bob, &alice, "Hi").await;
|
||||
|
||||
// We don't need bob anymore, let's make sure it's not accidentally used
|
||||
drop(bob);
|
||||
|
||||
// Assume Alice receives an email from bob@example.net with
|
||||
// correct DKIM -> `set_dkim_works()` was called
|
||||
set_dkim_works_timestamp(&alice, "example.net", time()).await?;
|
||||
// And Alice knows her server's authserv-id
|
||||
alice
|
||||
.set_config(Config::AuthservIdCandidates, Some("example.org"))
|
||||
.await?;
|
||||
|
||||
tcm.section("An attacker, bob2, sends a from-forged email to Alice!");
|
||||
|
||||
let bob2 = tcm.unconfigured().await;
|
||||
bob2.configure_addr("bob@example.net").await;
|
||||
e2ee::ensure_secret_key_exists(&bob2).await?;
|
||||
|
||||
let chat = bob2.create_chat(&alice).await;
|
||||
let mut sent = bob2
|
||||
.send_text(chat.id, "Please send me lots of money")
|
||||
.await;
|
||||
|
||||
sent.payload
|
||||
.insert_str(0, "Authentication-Results: example.org; dkim=fail");
|
||||
|
||||
let received = alice.recv_msg(&sent).await;
|
||||
|
||||
// Assert that the error tells the user about the problem
|
||||
assert!(received.error.unwrap().contains("DKIM failed"));
|
||||
|
||||
let bob_state = Peerstate::from_addr(&alice, "bob@example.net")
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
// Also check that the keypair was not changed
|
||||
assert_eq!(
|
||||
bob_state.public_key.unwrap(),
|
||||
test_utils::bob_keypair().public
|
||||
);
|
||||
|
||||
// Since Alice didn't change the key, Bob can't read her message
|
||||
let received = tcm
|
||||
.try_send_recv(&alice, &bob2, "My credit card number is 1234")
|
||||
.await;
|
||||
assert!(!received.text.as_ref().unwrap().contains("1234"));
|
||||
assert!(received.error.is_some());
|
||||
|
||||
tcm.section("Turns out bob2 wasn't an attacker at all, Bob just has a new phone and DKIM just stopped working.");
|
||||
tcm.section("To fix the key problems, Bob scans Alice's QR code.");
|
||||
|
||||
let qr = get_securejoin_qr(&alice.ctx, None).await.unwrap();
|
||||
join_securejoin(&bob2.ctx, &qr).await.unwrap();
|
||||
|
||||
loop {
|
||||
if let Some(mut sent) = bob2.pop_sent_msg_opt(Duration::ZERO).await {
|
||||
sent.payload
|
||||
.insert_str(0, "Authentication-Results: example.org; dkim=fail");
|
||||
alice.recv_msg(&sent).await;
|
||||
} else if let Some(sent) = alice.pop_sent_msg_opt(Duration::ZERO).await {
|
||||
bob2.recv_msg(&sent).await;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately, securejoin currently doesn't work with authres-checking,
|
||||
// so these checks would fail:
|
||||
|
||||
// let contact_bob = alice.add_or_lookup_contact(&bob2).await;
|
||||
// assert_eq!(
|
||||
// contact_bob.is_verified(&alice.ctx).await.unwrap(),
|
||||
// VerifiedStatus::BidirectVerified
|
||||
// );
|
||||
|
||||
// let contact_alice = bob2.add_or_lookup_contact(&alice).await;
|
||||
// assert_eq!(
|
||||
// contact_alice.is_verified(&bob2.ctx).await.unwrap(),
|
||||
// VerifiedStatus::BidirectVerified
|
||||
// );
|
||||
|
||||
// // Bob can read Alice's messages again
|
||||
// let received = tcm
|
||||
// .try_send_recv(&alice, &bob2, "Can you read this again?")
|
||||
// .await;
|
||||
// assert_eq!(received.text.as_ref().unwrap(), "Can you read this again?");
|
||||
// assert!(received.error.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -3702,11 +3702,11 @@ mod tests {
|
||||
|
||||
// create group and sync it to the second device
|
||||
let a1_chat_id = create_group_chat(&a1, ProtectionStatus::Unprotected, "foo").await?;
|
||||
a1.send_text(a1_chat_id, "ho!").await;
|
||||
let sent = a1.send_text(a1_chat_id, "ho!").await;
|
||||
let a1_msg = a1.get_last_msg().await;
|
||||
let a1_chat = Chat::load_from_db(&a1, a1_chat_id).await?;
|
||||
|
||||
let a2_msg = a2.recv_msg(&a1.pop_sent_msg().await).await;
|
||||
let a2_msg = a2.recv_msg(&sent).await;
|
||||
let a2_chat_id = a2_msg.chat_id;
|
||||
let a2_chat = Chat::load_from_db(&a2, a2_chat_id).await?;
|
||||
|
||||
|
||||
@@ -184,6 +184,12 @@ pub enum Config {
|
||||
/// In a future versions, this switch may be removed.
|
||||
#[strum(props(default = "0"))]
|
||||
SendSyncMsgs,
|
||||
|
||||
/// Space-separated list of all the authserv-ids which we believe
|
||||
/// may be the one of our email server.
|
||||
///
|
||||
/// See `crate::authres::update_authservid_candidates`.
|
||||
AuthservIdCandidates,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -4,12 +4,13 @@ use std::collections::HashSet;
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use mailparse::ParsedMail;
|
||||
use mailparse::SingleInfo;
|
||||
|
||||
use crate::aheader::Aheader;
|
||||
use crate::authres;
|
||||
use crate::authres::handle_authres;
|
||||
use crate::contact::addr_cmp;
|
||||
use crate::context::Context;
|
||||
use crate::headerdef::HeaderDef;
|
||||
use crate::headerdef::HeaderDefMap;
|
||||
use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
|
||||
use crate::keyring::Keyring;
|
||||
use crate::log::LogExt;
|
||||
@@ -55,35 +56,43 @@ pub async fn try_decrypt(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_decryption_info(
|
||||
pub async fn prepare_decryption(
|
||||
context: &Context,
|
||||
mail: &ParsedMail<'_>,
|
||||
from: &[SingleInfo],
|
||||
message_time: i64,
|
||||
) -> Result<DecryptionInfo> {
|
||||
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();
|
||||
let from = if let Some(f) = from.first() {
|
||||
&f.addr
|
||||
} else {
|
||||
return Ok(DecryptionInfo::default());
|
||||
};
|
||||
|
||||
let autocrypt_header = Aheader::from_headers(&from, &mail.headers)
|
||||
let autocrypt_header = Aheader::from_headers(from, &mail.headers)
|
||||
.ok_or_log_msg(context, "Failed to parse Autocrypt header")
|
||||
.flatten();
|
||||
|
||||
let peerstate =
|
||||
get_autocrypt_peerstate(context, &from, autocrypt_header.as_ref(), message_time).await?;
|
||||
let dkim_results = handle_authres(context, mail, from, message_time).await?;
|
||||
|
||||
let peerstate = get_autocrypt_peerstate(
|
||||
context,
|
||||
from,
|
||||
autocrypt_header.as_ref(),
|
||||
message_time,
|
||||
dkim_results.allow_keychange,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(DecryptionInfo {
|
||||
from,
|
||||
from: from.to_string(),
|
||||
autocrypt_header,
|
||||
peerstate,
|
||||
message_time,
|
||||
dkim_results,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DecryptionInfo {
|
||||
/// The From address. This is the address from the unnencrypted, outer
|
||||
/// From header.
|
||||
@@ -96,6 +105,7 @@ pub struct DecryptionInfo {
|
||||
/// means out-of-order message arrival, We don't modify the
|
||||
/// peerstate in this case.
|
||||
pub message_time: i64,
|
||||
pub(crate) dkim_results: authres::DkimResults,
|
||||
}
|
||||
|
||||
/// Returns a reference to the encrypted payload of a ["Mixed
|
||||
@@ -263,12 +273,16 @@ fn keyring_from_peerstate(peerstate: &Option<Peerstate>) -> Keyring<SignedPublic
|
||||
/// If we already know this fingerprint from another contact's peerstate, return that
|
||||
/// peerstate in order to make AEAP work, but don't save it into the db yet.
|
||||
///
|
||||
/// The param `allow_change` is used to prevent the autocrypt key from being changed
|
||||
/// if we suspect that the message may be forged and have a spoofed sender identity.
|
||||
///
|
||||
/// Returns updated peerstate.
|
||||
pub(crate) async fn get_autocrypt_peerstate(
|
||||
context: &Context,
|
||||
from: &str,
|
||||
autocrypt_header: Option<&Aheader>,
|
||||
message_time: i64,
|
||||
allow_change: bool,
|
||||
) -> Result<Option<Peerstate>> {
|
||||
let mut peerstate;
|
||||
|
||||
@@ -289,8 +303,15 @@ pub(crate) async fn get_autocrypt_peerstate(
|
||||
|
||||
if let Some(ref mut peerstate) = peerstate {
|
||||
if addr_cmp(&peerstate.addr, from) {
|
||||
peerstate.apply_header(header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
if allow_change {
|
||||
peerstate.apply_header(header, message_time);
|
||||
peerstate.save_to_db(&context.sql, false).await?;
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
"Refusing to update existing peerstate of {}", &peerstate.addr
|
||||
);
|
||||
}
|
||||
}
|
||||
// If `peerstate.addr` and `from` differ, this means that
|
||||
// someone is using the same key but a different addr, probably
|
||||
|
||||
@@ -63,6 +63,11 @@ pub enum HeaderDef {
|
||||
Sender,
|
||||
EphemeralTimer,
|
||||
Received,
|
||||
|
||||
/// A header that includes the results of the DKIM, SPF and DMARC checks.
|
||||
/// See <https://datatracker.ietf.org/doc/html/rfc8601>
|
||||
AuthenticationResults,
|
||||
|
||||
_TestHeader,
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use ::pgp::types::KeyTrait;
|
||||
use anyhow::{bail, ensure, format_err, Context as _, Result};
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use futures::StreamExt;
|
||||
use futures_lite::FutureExt;
|
||||
use rand::{thread_rng, Rng};
|
||||
use tokio::fs::{self, File};
|
||||
@@ -17,7 +17,6 @@ use crate::chat::{self, delete_and_reset_all_device_msgs, ChatId};
|
||||
use crate::config::Config;
|
||||
use crate::contact::ContactId;
|
||||
use crate::context::Context;
|
||||
use crate::e2ee;
|
||||
use crate::events::EventType;
|
||||
use crate::key::{self, DcKey, DcSecretKey, SignedPublicKey, SignedSecretKey};
|
||||
use crate::log::LogExt;
|
||||
@@ -31,6 +30,7 @@ use crate::tools::{
|
||||
create_folder, delete_file, get_filesuffix_lc, open_file_std, read_file, time, write_file,
|
||||
EmailAddress,
|
||||
};
|
||||
use crate::{e2ee, tools};
|
||||
|
||||
// Name of the database file in the backup.
|
||||
const DBFILE_BACKUP_NAME: &str = "dc_database_backup.sqlite";
|
||||
@@ -576,10 +576,7 @@ async fn export_backup_inner(
|
||||
.append_path_with_name(temp_db_path, DBFILE_BACKUP_NAME)
|
||||
.await?;
|
||||
|
||||
let read_dir: Vec<_> =
|
||||
tokio_stream::wrappers::ReadDirStream::new(fs::read_dir(context.get_blobdir()).await?)
|
||||
.try_collect()
|
||||
.await?;
|
||||
let read_dir = tools::read_dir(context.get_blobdir()).await?;
|
||||
let count = read_dir.len();
|
||||
let mut written_files = 0;
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ mod update_helper;
|
||||
pub mod webxdc;
|
||||
#[macro_use]
|
||||
mod dehtml;
|
||||
mod authres;
|
||||
mod color;
|
||||
pub mod html;
|
||||
pub mod plaintext;
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::blob::BlobObject;
|
||||
use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
|
||||
use crate::contact::{addr_cmp, addr_normalize, ContactId};
|
||||
use crate::context::Context;
|
||||
use crate::decrypt::{create_decryption_info, try_decrypt};
|
||||
use crate::decrypt::{prepare_decryption, try_decrypt};
|
||||
use crate::dehtml::dehtml;
|
||||
use crate::events::EventType;
|
||||
use crate::format_flowed::unformat_flowed;
|
||||
@@ -178,7 +178,7 @@ impl MimeMessage {
|
||||
.get_header_value(HeaderDef::Date)
|
||||
.and_then(|v| mailparse::dateparse(&v).ok())
|
||||
.unwrap_or_default();
|
||||
let hop_info = parse_receive_headers(&mail.get_headers());
|
||||
let mut hop_info = parse_receive_headers(&mail.get_headers());
|
||||
|
||||
let mut headers = Default::default();
|
||||
let mut recipients = Default::default();
|
||||
@@ -220,7 +220,9 @@ impl MimeMessage {
|
||||
let mut mail_raw = Vec::new();
|
||||
let mut gossiped_addr = Default::default();
|
||||
let mut from_is_signed = false;
|
||||
let mut decryption_info = create_decryption_info(context, &mail, message_time).await?;
|
||||
let mut decryption_info = prepare_decryption(context, &mail, &from, message_time).await?;
|
||||
hop_info += "\n\n";
|
||||
hop_info += &decryption_info.dkim_results.to_string();
|
||||
|
||||
// `signatures` is non-empty exactly if the message was encrypted and correctly signed.
|
||||
let (mail, signatures, warn_empty_signature) =
|
||||
@@ -369,6 +371,11 @@ impl MimeMessage {
|
||||
parser.heuristically_parse_ndn(context).await;
|
||||
parser.parse_headers(context).await?;
|
||||
|
||||
if !decryption_info.dkim_results.allow_keychange {
|
||||
for part in parser.parts.iter_mut() {
|
||||
part.error = Some("Seems like DKIM failed, this either is an attack or (more likely) a bug in Authentication-Results checking. Please tell us about this at https://support.delta.chat.".to_string());
|
||||
}
|
||||
}
|
||||
if warn_empty_signature && parser.signatures.is_empty() {
|
||||
for part in parser.parts.iter_mut() {
|
||||
part.error = Some("No valid signature".to_string());
|
||||
|
||||
@@ -609,6 +609,13 @@ CREATE INDEX smtp_messageid ON imap(rfc724_mid);
|
||||
92
|
||||
).await?;
|
||||
}
|
||||
if dbversion < 93 {
|
||||
sql.execute_migration(
|
||||
"CREATE TABLE sending_domains(domain TEXT PRIMARY KEY, dkim_works INTEGER DEFAULT 0);",
|
||||
93,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let new_version = sql
|
||||
.get_raw_config_int(VERSION_CFG)
|
||||
|
||||
@@ -74,6 +74,14 @@ impl TestContextManager {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Creates a new unconfigured test account.
|
||||
pub async fn unconfigured(&mut self) -> TestContext {
|
||||
TestContext::builder()
|
||||
.with_log_sink(self.log_tx.clone())
|
||||
.build()
|
||||
.await
|
||||
}
|
||||
|
||||
/// Writes info events to the log that mark a section, e.g.:
|
||||
///
|
||||
/// ========== `msg` goes here ==========
|
||||
@@ -89,19 +97,23 @@ impl TestContextManager {
|
||||
/// - Let the other TestContext receive it and accept the chat
|
||||
/// - Assert that the message arrived
|
||||
pub async fn send_recv_accept(&self, from: &TestContext, to: &TestContext, msg: &str) {
|
||||
let received_msg = self.try_send_recv(from, to, msg).await;
|
||||
assert_eq!(received_msg.text.as_ref().unwrap(), msg);
|
||||
received_msg.chat_id.accept(to).await.unwrap();
|
||||
}
|
||||
|
||||
/// - Let one TestContext send a message
|
||||
/// - Let the other TestContext receive it
|
||||
pub async fn try_send_recv(&self, from: &TestContext, to: &TestContext, msg: &str) -> Message {
|
||||
self.section(&format!(
|
||||
"{} sends a message '{}' to {}",
|
||||
from.name(),
|
||||
msg,
|
||||
to.name()
|
||||
));
|
||||
|
||||
let chat = from.create_chat(to).await;
|
||||
let sent = from.send_text(chat.id, msg).await;
|
||||
|
||||
let received_msg = to.recv_msg(&sent).await;
|
||||
received_msg.chat_id.accept(to).await.unwrap();
|
||||
assert_eq!(received_msg.text.unwrap(), msg);
|
||||
to.recv_msg(&sent).await
|
||||
}
|
||||
|
||||
pub async fn change_addr(&self, test_context: &TestContext, new_addr: &str) {
|
||||
@@ -369,6 +381,12 @@ impl TestContext {
|
||||
///
|
||||
/// Panics if there is no message or on any error.
|
||||
pub async fn pop_sent_msg(&self) -> SentMessage {
|
||||
self.pop_sent_msg_opt(Duration::from_secs(3))
|
||||
.await
|
||||
.expect("no sent message found in jobs table")
|
||||
}
|
||||
|
||||
pub async fn pop_sent_msg_opt(&self, timeout: Duration) -> Option<SentMessage> {
|
||||
let start = Instant::now();
|
||||
let (rowid, msg_id, payload, recipients) = loop {
|
||||
let row = self
|
||||
@@ -393,25 +411,25 @@ impl TestContext {
|
||||
if let Some(row) = row {
|
||||
break row;
|
||||
}
|
||||
if start.elapsed() < Duration::from_secs(3) {
|
||||
if start.elapsed() < timeout {
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
} else {
|
||||
panic!("no sent message found in jobs table");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
self.ctx
|
||||
.sql
|
||||
.execute("DELETE FROM jobs WHERE id=?;", paramsv![rowid])
|
||||
.execute("DELETE FROM smtp WHERE id=?;", paramsv![rowid])
|
||||
.await
|
||||
.expect("failed to remove job");
|
||||
update_msg_state(&self.ctx, msg_id, MessageState::OutDelivered)
|
||||
.await
|
||||
.expect("failed to update message state");
|
||||
SentMessage {
|
||||
Some(SentMessage {
|
||||
payload,
|
||||
sender_msg_id: msg_id,
|
||||
recipients,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a message.
|
||||
@@ -725,7 +743,7 @@ impl Drop for LogSink {
|
||||
/// passed through a SMTP-IMAP pipeline.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SentMessage {
|
||||
payload: String,
|
||||
pub payload: String,
|
||||
recipients: String,
|
||||
pub sender_msg_id: MsgId,
|
||||
}
|
||||
|
||||
17
src/tools.rs
17
src/tools.rs
@@ -12,7 +12,7 @@ use std::time::{Duration, SystemTime};
|
||||
|
||||
use anyhow::{bail, Error, Result};
|
||||
use chrono::{Local, TimeZone};
|
||||
use futures::StreamExt;
|
||||
use futures::{StreamExt, TryStreamExt};
|
||||
use mailparse::dateparse;
|
||||
use mailparse::headers::Headers;
|
||||
use mailparse::MailHeaderMap;
|
||||
@@ -495,6 +495,13 @@ pub fn open_file_std<P: AsRef<std::path::Path>>(
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_dir(path: &Path) -> Result<Vec<fs::DirEntry>> {
|
||||
let res = tokio_stream::wrappers::ReadDirStream::new(fs::read_dir(path).await?)
|
||||
.try_collect()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub(crate) fn time() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
@@ -715,7 +722,9 @@ hi
|
||||
Message-ID: 2dfdbde7@example.org
|
||||
|
||||
Hop: From: localhost; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:22 +0000
|
||||
Hop: From: hq5.merlinux.eu; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:25 +0000";
|
||||
Hop: From: hq5.merlinux.eu; By: hq5.merlinux.eu; Date: Sat, 14 Sep 2019 17:00:25 +0000
|
||||
|
||||
DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
|
||||
check_parse_receive_headers_integration(raw, expected).await;
|
||||
|
||||
let raw = include_bytes!("../test-data/message/encrypted_with_received_headers.eml");
|
||||
@@ -732,7 +741,9 @@ Message-ID: Mr.adQpEwndXLH.LPDdlFVJ7wG@example.net
|
||||
|
||||
Hop: From: [127.0.0.1]; By: mail.example.org; Date: Mon, 27 Dec 2021 11:21:21 +0000
|
||||
Hop: From: mout.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 +0000
|
||||
Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 +0000";
|
||||
Hop: From: hq5.example.org; By: hq5.example.org; Date: Mon, 27 Dec 2021 11:21:22 +0000
|
||||
|
||||
DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
|
||||
check_parse_receive_headers_integration(raw, expected).await;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas106.aol.mail.ne1.yahoo.com;
|
||||
dkim=pass header.i=@buzon.uy header.s=2019;
|
||||
spf=pass smtp.mailfrom=buzon.uy;
|
||||
dmarc=pass(p=REJECT) header.from=buzon.uy;
|
||||
From: <alice@buzon.uy>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas206.aol.mail.ne1.yahoo.com;
|
||||
dkim=unknown;
|
||||
spf=none smtp.mailfrom=delta.blinzeln.de;
|
||||
dmarc=unknown header.from=delta.blinzeln.de;
|
||||
From: <alice@delta.blinzeln.de>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas210.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@disroot.org header.s=mail;
|
||||
spf=pass smtp.mailfrom=disroot.org;
|
||||
dmarc=pass(p=QUARANTINE) header.from=disroot.org;
|
||||
From: <alice@disroot.org>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,7 @@
|
||||
Authentication-Results: atlas105.aol.mail.ne1.yahoo.com;
|
||||
dkim=pass header.i=@fastmail.com header.s=fm2;
|
||||
dkim=pass header.i=@messagingengine.com header.s=fm2;
|
||||
spf=pass smtp.mailfrom=fastmail.com;
|
||||
dmarc=pass(p=NONE,sp=NONE) header.from=fastmail.com;
|
||||
From: <alice@fastmail.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas-baseline-production.v2-mail-prod1-gq1.omega.yahoo.com;
|
||||
dkim=pass header.i=@gmail.com header.s=20210112;
|
||||
spf=pass smtp.mailfrom=gmail.com;
|
||||
dmarc=pass(p=NONE,sp=QUARANTINE) header.from=gmail.com;
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,8 @@
|
||||
Authentication-Results: atlas112.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@hotmail.com header.s=selector1;
|
||||
spf=pass smtp.mailfrom=hotmail.com;
|
||||
dmarc=pass(p=NONE) header.from=hotmail.com;
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas101.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@icloud.com header.s=1a1hai;
|
||||
spf=pass smtp.mailfrom=icloud.com;
|
||||
dmarc=pass(p=QUARANTINE,sp=QUARANTINE) header.from=icloud.com;
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas-production.v2-mail-prod1-gq1.omega.yahoo.com;
|
||||
dkim=pass header.i=@ik.me header.s=20200325;
|
||||
spf=pass smtp.mailfrom=ik.me;
|
||||
dmarc=pass(p=REJECT) header.from=ik.me;
|
||||
From: <alice@ik.me>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas104.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@mail.ru header.s=mail4;
|
||||
spf=pass smtp.mailfrom=mail.ru;
|
||||
dmarc=pass(p=REJECT) header.from=mail.ru;
|
||||
From: <alice@mail.ru>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas211.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@mailo.com header.s=mailo;
|
||||
spf=pass smtp.mailfrom=mailo.com;
|
||||
dmarc=pass(p=NONE) header.from=mailo.com;
|
||||
From: <alice@mailo.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,8 @@
|
||||
Authentication-Results: atlas-production.v2-mail-prod1-gq1.omega.yahoo.com;
|
||||
dkim=pass header.i=@outlook.com header.s=selector1;
|
||||
spf=pass smtp.mailfrom=outlook.com;
|
||||
dmarc=pass(p=NONE,sp=QUARANTINE) header.from=outlook.com;
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@outlook.com>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas-production.v2-mail-prod1-gq1.omega.yahoo.com;
|
||||
dkim=pass header.i=@posteo.de header.s=2017;
|
||||
spf=pass smtp.mailfrom=posteo.de;
|
||||
dmarc=pass(p=NONE) header.from=posteo.de;
|
||||
From: <alice@posteo.de>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas114.aol.mail.bf1.yahoo.com;
|
||||
dkim=pass header.i=@yandex.ru header.s=mail;
|
||||
spf=pass smtp.mailfrom=yandex.ru;
|
||||
dmarc=pass(p=NONE) header.from=yandex.ru;
|
||||
From: <alice@yandex.ru>
|
||||
To: <alice@aol.com>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: atlas206.aol.mail.ne1.yahoo.com;
|
||||
dkim=unknown;
|
||||
spf=none smtp.mailfrom=delta.blinzeln.de;
|
||||
dmarc=unknown header.from=delta.blinzeln.de;
|
||||
From: forged-authres-added@example.com
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=aol.com header.i=@aol.com header.b="sjmqxpKe";
|
||||
dkim-atps=neutral
|
||||
From: <alice@aol.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,3 @@
|
||||
From: <alice@delta.blinzeln.de>
|
||||
To: <alice@buzon.uy>
|
||||
Authentication-Results: secure-mailgate.com; auth=pass smtp.auth=91.203.111.88@webbox222.server-home.org
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; secure) header.d=disroot.org header.i=@disroot.org header.b="L9SmOHOj";
|
||||
dkim-atps=neutral
|
||||
From: <alice@disroot.org>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=fastmail.com header.i=@fastmail.com header.b="kLB05is1";
|
||||
dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="B8mfR89g";
|
||||
dkim-atps=neutral
|
||||
From: <alice@fastmail.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="Ngf1X5eN";
|
||||
dkim-atps=neutral
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,7 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=hotmail.com header.i=@hotmail.com header.b="dEHn9Szj";
|
||||
dkim-atps=neutral
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=icloud.com header.i=@icloud.com header.b="rAXD4xVN";
|
||||
dkim-atps=neutral
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (1024-bit key; secure) header.d=ik.me header.i=@ik.me header.b="EWWQpVZX";
|
||||
dkim-atps=neutral
|
||||
From: <alice@ik.me>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; secure) header.d=mail.de header.i=@mail.de header.b="18cRkjHf";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mail.de>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=mail.ru header.i=@mail.ru header.b="uXBGAnnn";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mail.ru>
|
||||
To: <alice@buzon.uy>
|
||||
Authentication-Results: smtpng1.m.smailru.net; auth=pass smtp.auth=alice@mail.ru smtp.mailfrom=alice@mail.ru
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=mailo.com header.i=@mailo.com header.b="awx9eOw9";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mailo.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,7 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=outlook.com header.i=@outlook.com header.b="Uq5LH/n/";
|
||||
dkim-atps=neutral
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@outlook.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; secure) header.d=posteo.de header.i=@posteo.de header.b="AyOucyBM";
|
||||
dkim-atps=neutral
|
||||
From: <alice@posteo.de>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (1024-bit key; secure) header.d=riseup.net header.i=@riseup.net header.b="eQhRD1BM";
|
||||
dkim-atps=neutral
|
||||
From: <alice@riseup.net>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=yahoo.com header.i=@yahoo.com header.b="a1T2PpDI";
|
||||
dkim-atps=neutral
|
||||
From: <alice@yahoo.com>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: mail.buzon.uy;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=yandex.ru header.i=@yandex.ru header.b="oGBtMMzR";
|
||||
dkim-atps=neutral
|
||||
Authentication-Results: iva4-143b1447cf50.qloud-c.yandex.net; dkim=pass header.i=@yandex.ru
|
||||
From: <alice@yandex.ru>
|
||||
To: <alice@buzon.uy>
|
||||
@@ -0,0 +1,3 @@
|
||||
From: forged-authres-added@example.com
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@aol.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@buzon.uy>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@disroot.org>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@fastmail.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,4 @@
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@ik.me>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@mail.de>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,3 @@
|
||||
From: <alice@mail.ru>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
Authentication-Results: smtpng1.m.smailru.net; auth=pass smtp.auth=alice@mail.ru smtp.mailfrom=alice@mail.ru
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@mailo.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,4 @@
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@outlook.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@posteo.de>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@riseup.net>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,2 @@
|
||||
From: <alice@yahoo.com>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,3 @@
|
||||
Authentication-Results: iva4-143b1447cf50.qloud-c.yandex.net; dkim=pass header.i=@yandex.ru
|
||||
From: <alice@yandex.ru>
|
||||
To: <alice@delta.blinzeln.de>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=aol.com header.i=@aol.com header.b="DBDqUGR2";
|
||||
dkim-atps=neutral
|
||||
From: <alice@aol.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=buzon.uy header.i=@buzon.uy header.b="VVA3HpHe";
|
||||
dkim-atps=neutral
|
||||
From: <alice@buzon.uy>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,3 @@
|
||||
From: <alice@delta.blinzeln.de>
|
||||
To: <alice@disroot.org>
|
||||
Authentication-Results: secure-mailgate.com; auth=pass smtp.auth=91.203.111.88@webbox222.server-home.org
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=fastmail.com header.i=@fastmail.com header.b="OFgq9UWZ";
|
||||
dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="B52d7C0G";
|
||||
dkim-atps=neutral
|
||||
From: <alice@fastmail.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=gmail.com header.i=@gmail.com header.b="lxlrOeGY";
|
||||
dkim-atps=neutral
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,7 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=hotmail.com header.i=@hotmail.com header.b="VdDCDs55";
|
||||
dkim-atps=neutral
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=icloud.com header.i=@icloud.com header.b="kD59vbQH";
|
||||
dkim-atps=neutral
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=ik.me header.i=@ik.me header.b="YBLXQ2kp";
|
||||
dkim-atps=neutral
|
||||
From: <alice@ik.me>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=mail.de header.i=@mail.de header.b="3ha9obSK";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mail.de>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=mail.ru header.i=@mail.ru header.b="IrDU+LTI";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mail.ru>
|
||||
To: <alice@disroot.org>
|
||||
Authentication-Results: smtpng1.m.smailru.net; auth=pass smtp.auth=alice@mail.ru smtp.mailfrom=alice@mail.ru
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=mailo.com header.i=@mailo.com header.b="WgsA5pwT";
|
||||
dkim-atps=neutral
|
||||
From: <alice@mailo.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,7 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=outlook.com header.i=@outlook.com header.b="UtZoPK7Z";
|
||||
dkim-atps=neutral
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@outlook.com>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (2048-bit key; unprotected) header.d=posteo.de header.i=@posteo.de header.b="R67Ab9Vj";
|
||||
dkim-atps=neutral
|
||||
From: <alice@posteo.de>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,5 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=riseup.net header.i=@riseup.net header.b="fFYxwl1W";
|
||||
dkim-atps=neutral
|
||||
From: <alice@riseup.net>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,6 @@
|
||||
Authentication-Results: disroot.org;
|
||||
dkim=pass (1024-bit key; unprotected) header.d=yandex.ru header.i=@yandex.ru header.b="CvyR5Vj/";
|
||||
dkim-atps=neutral
|
||||
Authentication-Results: iva4-143b1447cf50.qloud-c.yandex.net; dkim=pass header.i=@yandex.ru
|
||||
From: <alice@yandex.ru>
|
||||
To: <alice@disroot.org>
|
||||
@@ -0,0 +1,3 @@
|
||||
From: forged-authres-added@example.com
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@aol.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=aol.com header.s=a2048 header.b=HlBq3Lmt;
|
||||
dmarc=pass (policy=reject) header.from=aol.com;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@aol.com designates 77.238.178.146 as permitted sender) smtp.mailfrom=alice@aol.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@buzon.uy>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=buzon.uy header.s=2019 header.b=XfN4sE6M;
|
||||
dmarc=pass (policy=reject) header.from=buzon.uy;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@buzon.uy designates 185.101.93.79 as permitted sender) smtp.mailfrom=alice@buzon.uy
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@delta.blinzeln.de>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=none;
|
||||
dmarc=none;
|
||||
spf=none (mail2.ecloud.global: domain of alice@delta.blinzeln.de has no SPF policy when checking 192.162.87.117) smtp.mailfrom=alice@delta.blinzeln.de
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@disroot.org>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=disroot.org header.s=mail header.b=Vf7JxMcu;
|
||||
dmarc=pass (policy=quarantine) header.from=disroot.org;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@disroot.org designates 178.21.23.139 as permitted sender) smtp.mailfrom=alice@disroot.org
|
||||
@@ -0,0 +1,7 @@
|
||||
From: <alice@fastmail.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=fastmail.com header.s=fm2 header.b=bQ080jJU;
|
||||
dkim=pass header.d=messagingengine.com header.s=fm2 header.b=FVyMuSGb;
|
||||
dmarc=pass (policy=none) header.from=fastmail.com;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@fastmail.com designates 66.111.4.28 as permitted sender) smtp.mailfrom=alice@fastmail.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=gmail.com header.s=20210112 header.b=f2AxVaaA;
|
||||
dmarc=pass (policy=none) header.from=gmail.com;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@gmail.com designates 2a00:1450:4864:20::443 as permitted sender) smtp.mailfrom=alice@gmail.com
|
||||
@@ -0,0 +1,9 @@
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=hotmail.com header.s=selector1 header.b="ECc21y/J";
|
||||
arc=pass ("microsoft.com:s=arcselector9901:i=1");
|
||||
dmarc=pass (policy=none) header.from=hotmail.com;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@hotmail.com designates 40.92.68.54 as permitted sender) smtp.mailfrom=alice@hotmail.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=icloud.com header.s=1a1hai header.b=enuQcpfH;
|
||||
dmarc=pass (policy=quarantine) header.from=icloud.com;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@icloud.com designates 17.57.155.16 as permitted sender) smtp.mailfrom=alice@icloud.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@ik.me>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=ik.me header.s=20200325 header.b=E5lGMzkQ;
|
||||
dmarc=pass (policy=reject) header.from=ik.me;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@ik.me designates 83.166.143.168 as permitted sender) smtp.mailfrom=alice@ik.me
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@mail.de>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=mail.de header.s=mailde202009 header.b=vVqLo0H5;
|
||||
dmarc=pass (policy=none) header.from=mail.de;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@mail.de designates 2001:868:100:600::216 as permitted sender) smtp.mailfrom=alice@mail.de
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@mail.ru>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=mail.ru header.s=mail4 header.b=heZ1ZiKV;
|
||||
dmarc=pass (policy=reject) header.from=mail.ru;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@mail.ru designates 94.100.181.251 as permitted sender) smtp.mailfrom=alice@mail.ru
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@mailo.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=mailo.com header.s=mailo header.b=HnhKNKUg;
|
||||
dmarc=pass (policy=none) header.from=mailo.com;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@mailo.com designates 213.182.54.15 as permitted sender) smtp.mailfrom=alice@mailo.com
|
||||
@@ -0,0 +1,9 @@
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@outlook.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail3.ecloud.global;
|
||||
dkim=pass header.d=outlook.com header.s=selector1 header.b=MqNsAJKf;
|
||||
arc=pass ("microsoft.com:s=arcselector9901:i=1");
|
||||
dmarc=pass (policy=none) header.from=outlook.com;
|
||||
spf=pass (mail3.ecloud.global: domain of alice@outlook.com designates 40.92.66.84 as permitted sender) smtp.mailfrom=alice@outlook.com
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@posteo.de>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=posteo.de header.s=2017 header.b=bMopHaXo;
|
||||
dmarc=pass (policy=none) header.from=posteo.de;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@posteo.de designates 185.67.36.66 as permitted sender) smtp.mailfrom=alice@posteo.de
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@riseup.net>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=riseup.net header.s=squak header.b="GUmo/IlI";
|
||||
dmarc=pass (policy=none) header.from=riseup.net;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@riseup.net designates 198.252.153.129 as permitted sender) smtp.mailfrom=alice@riseup.net
|
||||
@@ -0,0 +1,6 @@
|
||||
From: <alice@yahoo.com>
|
||||
To: <alice@e.email>
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
dkim=pass header.d=yahoo.com header.s=s2048 header.b=mRSwanl2;
|
||||
dmarc=pass (policy=reject) header.from=yahoo.com;
|
||||
spf=pass (mail2.ecloud.global: domain of alice@yahoo.com designates 77.238.178.146 as permitted sender) smtp.mailfrom=alice@yahoo.com
|
||||
@@ -0,0 +1,3 @@
|
||||
From: forged-authres-added@example.com
|
||||
Authentication-Results: mail2.ecloud.global;
|
||||
Authentication-Results: aaa.com; dkim=pass header.i=@example.com
|
||||
@@ -0,0 +1,39 @@
|
||||
ARC-Authentication-Results: i=1; mx1.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=sonic314-20.consmr.mail.ir2.yahoo.com
|
||||
policy.ptr=sonic314-20.consmr.mail.ir2.yahoo.com;
|
||||
bimi=none (No BIMI records found);
|
||||
arc=none (no signatures found);
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=aol.com header.i=@aol.com
|
||||
header.b=Y+EgdIPN header.a=rsa-sha256 header.s=a2048 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=reject
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=reject,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=aol.com;
|
||||
iprev=pass smtp.remote-ip=77.238.177.146
|
||||
(sonic314-20.consmr.mail.ir2.yahoo.com);
|
||||
spf=pass smtp.mailfrom=alice@aol.com
|
||||
smtp.helo=sonic314-20.consmr.mail.ir2.yahoo.com
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=sonic314-20.consmr.mail.ir2.yahoo.com
|
||||
policy.ptr=sonic314-20.consmr.mail.ir2.yahoo.com
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
bimi=none (No BIMI records found)
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=aol.com header.i=@aol.com
|
||||
header.b=Y+EgdIPN header.a=rsa-sha256 header.s=a2048 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=reject
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=reject,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=aol.com;
|
||||
iprev=pass smtp.remote-ip=77.238.177.146
|
||||
(sonic314-20.consmr.mail.ir2.yahoo.com);
|
||||
spf=pass smtp.mailfrom=alice@aol.com
|
||||
smtp.helo=sonic314-20.consmr.mail.ir2.yahoo.com
|
||||
From: <alice@aol.com>
|
||||
To: <alice@fastmail.com>
|
||||
@@ -0,0 +1,33 @@
|
||||
ARC-Authentication-Results: i=1; mx4.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=mail.buzon.uy policy.ptr=mail.buzon.uy;
|
||||
bimi=none (No BIMI records found);
|
||||
arc=none (no signatures found);
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=buzon.uy header.i=@buzon.uy
|
||||
header.b=ibjKHetx header.a=rsa-sha256 header.s=2019 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=reject
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=reject,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=buzon.uy;
|
||||
iprev=pass smtp.remote-ip=185.101.93.79 (mail.buzon.uy);
|
||||
spf=pass smtp.mailfrom=alice@buzon.uy smtp.helo=mail.buzon.uy
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=mail.buzon.uy policy.ptr=mail.buzon.uy
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
bimi=none (No BIMI records found)
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=buzon.uy header.i=@buzon.uy
|
||||
header.b=ibjKHetx header.a=rsa-sha256 header.s=2019 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=reject
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=reject,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=buzon.uy;
|
||||
iprev=pass smtp.remote-ip=185.101.93.79 (mail.buzon.uy);
|
||||
spf=pass smtp.mailfrom=alice@buzon.uy smtp.helo=mail.buzon.uy
|
||||
From: <alice@buzon.uy>
|
||||
To: <alice@fastmail.com>
|
||||
@@ -0,0 +1,38 @@
|
||||
ARC-Authentication-Results: i=1; mx1.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=nx184.node01.secure-mailgate.com
|
||||
policy.ptr=nx184.node01.secure-mailgate.com;
|
||||
bimi=skipped (DMARC did not pass);
|
||||
arc=none (no signatures found);
|
||||
dkim=none (no signatures found);
|
||||
dmarc=none policy.published-domain-policy=none
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=delta.blinzeln.de;
|
||||
iprev=pass smtp.remote-ip=89.22.108.184
|
||||
(nx184.node01.secure-mailgate.com);
|
||||
spf=none smtp.mailfrom=alice@delta.blinzeln.de
|
||||
smtp.helo=nx184.node01.secure-mailgate.com
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=nx184.node01.secure-mailgate.com
|
||||
policy.ptr=nx184.node01.secure-mailgate.com
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
bimi=skipped (DMARC did not pass)
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx1.messagingengine.com;
|
||||
dkim=none (no signatures found);
|
||||
dmarc=none policy.published-domain-policy=none
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=delta.blinzeln.de;
|
||||
iprev=pass smtp.remote-ip=89.22.108.184
|
||||
(nx184.node01.secure-mailgate.com);
|
||||
spf=none smtp.mailfrom=alice@delta.blinzeln.de
|
||||
smtp.helo=nx184.node01.secure-mailgate.com
|
||||
From: <alice@delta.blinzeln.de>
|
||||
To: <alice@fastmail.com>
|
||||
Authentication-Results: secure-mailgate.com; auth=pass smtp.auth=91.203.111.88@webbox222.server-home.org
|
||||
@@ -0,0 +1,35 @@
|
||||
ARC-Authentication-Results: i=1; mx5.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=knopi.disroot.org policy.ptr=knopi.disroot.org;
|
||||
bimi=none (No BIMI records found);
|
||||
arc=none (no signatures found);
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=disroot.org
|
||||
header.i=@disroot.org header.b=OQYRknPj header.a=rsa-sha256
|
||||
header.s=mail x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=disroot.org;
|
||||
iprev=pass smtp.remote-ip=178.21.23.139 (knopi.disroot.org);
|
||||
spf=pass smtp.mailfrom=alice@disroot.org smtp.helo=knopi.disroot.org
|
||||
Authentication-Results: mx5.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=knopi.disroot.org policy.ptr=knopi.disroot.org
|
||||
Authentication-Results: mx5.messagingengine.com;
|
||||
bimi=none (No BIMI records found)
|
||||
Authentication-Results: mx5.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx5.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=disroot.org
|
||||
header.i=@disroot.org header.b=OQYRknPj header.a=rsa-sha256
|
||||
header.s=mail x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=disroot.org;
|
||||
iprev=pass smtp.remote-ip=178.21.23.139 (knopi.disroot.org);
|
||||
spf=pass smtp.mailfrom=alice@disroot.org smtp.helo=knopi.disroot.org
|
||||
From: <alice@disroot.org>
|
||||
To: <alice@fastmail.com>
|
||||
@@ -0,0 +1,41 @@
|
||||
ARC-Authentication-Results: i=1; mx6.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=mail-wm1-f67.google.com
|
||||
policy.ptr=mail-wm1-f67.google.com;
|
||||
bimi=skipped (DMARC Policy is not at enforcement);
|
||||
arc=none (no signatures found);
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=gmail.com
|
||||
header.i=@gmail.com header.b=hr44hXYS header.a=rsa-sha256
|
||||
header.s=20210112 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=none
|
||||
policy.published-subdomain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,sp=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=gmail.com;
|
||||
iprev=pass smtp.remote-ip=209.85.128.67 (mail-wm1-f67.google.com);
|
||||
spf=pass smtp.mailfrom=alice@gmail.com
|
||||
smtp.helo=mail-wm1-f67.google.com
|
||||
Authentication-Results: mx6.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=mail-wm1-f67.google.com
|
||||
policy.ptr=mail-wm1-f67.google.com
|
||||
Authentication-Results: mx6.messagingengine.com;
|
||||
bimi=skipped (DMARC Policy is not at enforcement)
|
||||
Authentication-Results: mx6.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx6.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=gmail.com
|
||||
header.i=@gmail.com header.b=hr44hXYS header.a=rsa-sha256
|
||||
header.s=20210112 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=none
|
||||
policy.published-subdomain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,sp=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=gmail.com;
|
||||
iprev=pass smtp.remote-ip=209.85.128.67 (mail-wm1-f67.google.com);
|
||||
spf=pass smtp.mailfrom=alice@gmail.com
|
||||
smtp.helo=mail-wm1-f67.google.com
|
||||
From: <alice@gmail.com>
|
||||
To: <alice@fastmail.com>
|
||||
@@ -0,0 +1,45 @@
|
||||
ARC-Authentication-Results: i=2; mx2.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=fail smtp.helo=EUR05-DB8-obe.outbound.protection.outlook.com
|
||||
policy.ptr=mail-db8eur05olkn2101.outbound.protection.outlook.com;
|
||||
bimi=skipped (DMARC Policy is not at enforcement);
|
||||
arc=pass (as.1.microsoft.com=pass, ams.1.microsoft.com=pass)
|
||||
smtp.remote-ip=40.92.89.101;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=hotmail.com
|
||||
header.i=@hotmail.com header.b=FbLQTic7 header.a=rsa-sha256
|
||||
header.s=selector1 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=none
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=hotmail.com;
|
||||
iprev=pass smtp.remote-ip=40.92.89.101
|
||||
(mail-db8eur05olkn2101.outbound.protection.outlook.com);
|
||||
spf=pass smtp.mailfrom=alice@hotmail.com
|
||||
smtp.helo=EUR05-DB8-obe.outbound.protection.outlook.com
|
||||
Authentication-Results: mx2.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=fail smtp.helo=EUR05-DB8-obe.outbound.protection.outlook.com
|
||||
policy.ptr=mail-db8eur05olkn2101.outbound.protection.outlook.com
|
||||
Authentication-Results: mx2.messagingengine.com;
|
||||
bimi=skipped (DMARC Policy is not at enforcement)
|
||||
Authentication-Results: mx2.messagingengine.com;
|
||||
arc=pass (as.1.microsoft.com=pass, ams.1.microsoft.com=pass)
|
||||
smtp.remote-ip=40.92.89.101
|
||||
Authentication-Results: mx2.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=hotmail.com
|
||||
header.i=@hotmail.com header.b=FbLQTic7 header.a=rsa-sha256
|
||||
header.s=selector1 x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=none
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=none,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=hotmail.com;
|
||||
iprev=pass smtp.remote-ip=40.92.89.101
|
||||
(mail-db8eur05olkn2101.outbound.protection.outlook.com);
|
||||
spf=pass smtp.mailfrom=alice@hotmail.com
|
||||
smtp.helo=EUR05-DB8-obe.outbound.protection.outlook.com
|
||||
ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none;
|
||||
dkim=none; arc=none
|
||||
From: <alice@hotmail.com>
|
||||
To: <alice@fastmail.com>
|
||||
@@ -0,0 +1,41 @@
|
||||
ARC-Authentication-Results: i=1; mx4.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=qs51p00im-qukt01072701.me.com
|
||||
policy.ptr=qs51p00im-qukt01072701.me.com;
|
||||
bimi=declined (Domain declined to participate);
|
||||
arc=none (no signatures found);
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=icloud.com
|
||||
header.i=@icloud.com header.b=QwCPOZZR header.a=rsa-sha256
|
||||
header.s=1a1hai x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=quarantine
|
||||
policy.published-subdomain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=quarantine,sp=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=icloud.com;
|
||||
iprev=pass smtp.remote-ip=17.57.155.16 (qs51p00im-qukt01072701.me.com);
|
||||
spf=pass smtp.mailfrom=alice@icloud.com
|
||||
smtp.helo=qs51p00im-qukt01072701.me.com
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
x-csa=none;
|
||||
x-me-sender=none;
|
||||
x-ptr=pass smtp.helo=qs51p00im-qukt01072701.me.com
|
||||
policy.ptr=qs51p00im-qukt01072701.me.com
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
bimi=declined (Domain declined to participate)
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
arc=none (no signatures found)
|
||||
Authentication-Results: mx4.messagingengine.com;
|
||||
dkim=pass (2048-bit rsa key sha256) header.d=icloud.com
|
||||
header.i=@icloud.com header.b=QwCPOZZR header.a=rsa-sha256
|
||||
header.s=1a1hai x-bits=2048;
|
||||
dmarc=pass policy.published-domain-policy=quarantine
|
||||
policy.published-subdomain-policy=quarantine
|
||||
policy.applied-disposition=none policy.evaluated-disposition=none
|
||||
(p=quarantine,sp=quarantine,d=none,d.eval=none) policy.policy-from=p
|
||||
header.from=icloud.com;
|
||||
iprev=pass smtp.remote-ip=17.57.155.16 (qs51p00im-qukt01072701.me.com);
|
||||
spf=pass smtp.mailfrom=alice@icloud.com
|
||||
smtp.helo=qs51p00im-qukt01072701.me.com
|
||||
From: <alice@icloud.com>
|
||||
To: <alice@fastmail.com>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user