mirror of
https://github.com/mailscope/kumomta.git
synced 2026-08-18 18:38:18 +00:00
add msg:dkim_verify()
This method returns an array of AuthenticationResult reflecting the verification status. refs: https://github.com/KumoCorp/kumomta/issues/82
This commit is contained in:
Generated
+1
@@ -2904,6 +2904,7 @@ dependencies = [
|
||||
"chrono-tz",
|
||||
"config",
|
||||
"data-loader",
|
||||
"dns-resolver",
|
||||
"futures",
|
||||
"k9",
|
||||
"kumo-dkim",
|
||||
|
||||
@@ -50,6 +50,10 @@ pub enum Resolver {
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
pub async fn resolve_txt<N: IntoName + TryParseIp>(&self, name: N) -> anyhow::Result<Answer> {
|
||||
self.resolve(name, RecordType::TXT).await
|
||||
}
|
||||
|
||||
pub async fn resolve<N: IntoName + TryParseIp>(
|
||||
&self,
|
||||
name: N,
|
||||
|
||||
@@ -9,6 +9,7 @@ use nom::combinator::{all_consuming, map, opt, recognize};
|
||||
use nom::error::context;
|
||||
use nom::multi::{many0, many1, separated_list1};
|
||||
use nom::sequence::{delimited, preceded, separated_pair, terminated, tuple};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -1333,7 +1334,7 @@ impl Parser {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AuthenticationResults {
|
||||
pub serv_id: String,
|
||||
pub version: Option<u32>,
|
||||
@@ -1389,7 +1390,7 @@ impl EncodeHeaderValue for AuthenticationResults {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AuthenticationResult {
|
||||
pub method: String,
|
||||
pub method_version: Option<u32>,
|
||||
|
||||
@@ -14,6 +14,7 @@ config = {path="../config"}
|
||||
chrono = {version="0.4", default-features=false, features=["serde", "clock"]}
|
||||
chrono-tz = {version="0.8", features=["serde"]}
|
||||
data-loader = {path="../data-loader"}
|
||||
dns-resolver = {path="../dns-resolver"}
|
||||
futures = "0.3"
|
||||
kumo-log-types = {path="../kumo-log-types"}
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -5,10 +5,12 @@ use crate::EnvelopeAddress;
|
||||
use anyhow::Context;
|
||||
use chrono::{DateTime, Utc};
|
||||
use config::{any_err, from_lua_value};
|
||||
use dns_resolver::resolver::Resolver;
|
||||
use futures::future::BoxFuture;
|
||||
use futures::FutureExt;
|
||||
use kumo_log_types::rfc3464::Report;
|
||||
use kumo_log_types::rfc5965::ARFReport;
|
||||
use mailparsing::{Header, HeaderParseResult, MessageConformance, MimePart};
|
||||
use mailparsing::{AuthenticationResult, Header, HeaderParseResult, MessageConformance, MimePart};
|
||||
use mlua::{LuaSerdeExt, UserData, UserDataMethods};
|
||||
use prometheus::IntGauge;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -554,6 +556,67 @@ impl Message {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn dkim_verify(&self) -> anyhow::Result<Vec<AuthenticationResult>> {
|
||||
let resolver = dns_resolver::get_resolver();
|
||||
let data = self.get_data();
|
||||
let bytes = mailparsing::SharedString::try_from(data.as_ref().as_ref())?;
|
||||
|
||||
let parsed = mailparsing::Header::parse_headers(bytes.clone())?;
|
||||
if parsed
|
||||
.overall_conformance
|
||||
.contains(MessageConformance::NON_CANONICAL_LINE_ENDINGS)
|
||||
{
|
||||
return Ok(vec![AuthenticationResult {
|
||||
method: "dkim".to_string(),
|
||||
method_version: None,
|
||||
result: "permerror".to_string(),
|
||||
reason: Some("message has non-canonical line endings".to_string()),
|
||||
props: Default::default(),
|
||||
}]);
|
||||
}
|
||||
let message = kumo_dkim::ParsedEmail::HeaderOnlyParse { bytes, parsed };
|
||||
|
||||
let from = message
|
||||
.get_headers()
|
||||
.from()
|
||||
.map_err(any_err)?
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing or invalid From header"))?
|
||||
.0;
|
||||
if from.len() != 1 {
|
||||
anyhow::bail!(
|
||||
"From header must have a single sender, found {}",
|
||||
from.len()
|
||||
);
|
||||
}
|
||||
let from_domain = &from[0].address.domain;
|
||||
|
||||
struct ResolverAdapater {
|
||||
resolver: Arc<Resolver>,
|
||||
}
|
||||
|
||||
impl kumo_dkim::dns::Lookup for ResolverAdapater {
|
||||
fn lookup_txt<'a>(
|
||||
&'a self,
|
||||
name: &'a str,
|
||||
) -> BoxFuture<'a, Result<Vec<String>, kumo_dkim::DKIMError>> {
|
||||
Box::pin(async move {
|
||||
match self.resolver.resolve_txt(name).await {
|
||||
Ok(answer) => Ok(answer.as_txt()),
|
||||
Err(err) => Err(kumo_dkim::DKIMError::KeyUnavailable(format!("{err}"))),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
let results = kumo_dkim::verify_email_with_resolver(
|
||||
from_domain,
|
||||
&message,
|
||||
&ResolverAdapater { resolver },
|
||||
)
|
||||
.await?;
|
||||
Ok(results)
|
||||
}
|
||||
|
||||
pub fn parse_rfc3464(&self) -> anyhow::Result<Option<Report>> {
|
||||
let data = self.get_data();
|
||||
Report::parse(&data)
|
||||
@@ -876,6 +939,12 @@ impl UserData for Message {
|
||||
methods.add_method("dkim_sign", move |_, this, signer: Signer| {
|
||||
Ok(this.dkim_sign(&signer).map_err(any_err)?)
|
||||
});
|
||||
|
||||
methods.add_async_method("dkim_verify", |lua, this, ()| async move {
|
||||
let results = this.dkim_verify().await.map_err(any_err)?;
|
||||
lua.to_value(&results)
|
||||
});
|
||||
|
||||
methods.add_method(
|
||||
"prepend_header",
|
||||
move |_, this, (name, value): (String, String)| {
|
||||
|
||||
@@ -125,6 +125,9 @@ end)
|
||||
-- Called once the body has been received.
|
||||
-- For multi-recipient mail, this is called for each recipient.
|
||||
kumo.on('smtp_server_message_received', function(msg)
|
||||
local verify = msg:dkim_verify()
|
||||
print('dkim', kumo.json_encode(verify))
|
||||
|
||||
local failed = msg:check_fix_conformance(
|
||||
-- check for and reject messages with these issues:
|
||||
'MISSING_COLON_VALUE',
|
||||
|
||||
Reference in New Issue
Block a user