Author: Kevin Schoon [me@kevinschoon.com]
Hash: 331d7e8c1694b1ddddb3a99cd1c69c5e1bd20fcb
Timestamp: Wed, 04 Sep 2024 09:00:23 +0000 (1 month ago)

+253 -55 +/-8 browse
wire in spf verification, config options, refactor
1diff --git a/Cargo.lock b/Cargo.lock
2index d6d781b..889e49c 100644
3--- a/Cargo.lock
4+++ b/Cargo.lock
5 @@ -54,6 +54,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
6 checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
7
8 [[package]]
9+ name = "anstream"
10+ version = "0.6.15"
11+ source = "registry+https://github.com/rust-lang/crates.io-index"
12+ checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
13+ dependencies = [
14+ "anstyle",
15+ "anstyle-parse",
16+ "anstyle-query",
17+ "anstyle-wincon",
18+ "colorchoice",
19+ "is_terminal_polyfill",
20+ "utf8parse",
21+ ]
22+
23+ [[package]]
24+ name = "anstyle"
25+ version = "1.0.8"
26+ source = "registry+https://github.com/rust-lang/crates.io-index"
27+ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
28+
29+ [[package]]
30+ name = "anstyle-parse"
31+ version = "0.2.5"
32+ source = "registry+https://github.com/rust-lang/crates.io-index"
33+ checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
34+ dependencies = [
35+ "utf8parse",
36+ ]
37+
38+ [[package]]
39+ name = "anstyle-query"
40+ version = "1.1.1"
41+ source = "registry+https://github.com/rust-lang/crates.io-index"
42+ checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
43+ dependencies = [
44+ "windows-sys 0.52.0",
45+ ]
46+
47+ [[package]]
48+ name = "anstyle-wincon"
49+ version = "3.0.4"
50+ source = "registry+https://github.com/rust-lang/crates.io-index"
51+ checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
52+ dependencies = [
53+ "anstyle",
54+ "windows-sys 0.52.0",
55+ ]
56+
57+ [[package]]
58 name = "arbitrary"
59 version = "1.3.2"
60 source = "registry+https://github.com/rust-lang/crates.io-index"
61 @@ -187,6 +236,52 @@ dependencies = [
62 ]
63
64 [[package]]
65+ name = "clap"
66+ version = "4.5.16"
67+ source = "registry+https://github.com/rust-lang/crates.io-index"
68+ checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019"
69+ dependencies = [
70+ "clap_builder",
71+ "clap_derive",
72+ ]
73+
74+ [[package]]
75+ name = "clap_builder"
76+ version = "4.5.15"
77+ source = "registry+https://github.com/rust-lang/crates.io-index"
78+ checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6"
79+ dependencies = [
80+ "anstream",
81+ "anstyle",
82+ "clap_lex",
83+ "strsim",
84+ ]
85+
86+ [[package]]
87+ name = "clap_derive"
88+ version = "4.5.13"
89+ source = "registry+https://github.com/rust-lang/crates.io-index"
90+ checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
91+ dependencies = [
92+ "heck 0.5.0",
93+ "proc-macro2",
94+ "quote",
95+ "syn",
96+ ]
97+
98+ [[package]]
99+ name = "clap_lex"
100+ version = "0.7.2"
101+ source = "registry+https://github.com/rust-lang/crates.io-index"
102+ checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
103+
104+ [[package]]
105+ name = "colorchoice"
106+ version = "1.0.2"
107+ source = "registry+https://github.com/rust-lang/crates.io-index"
108+ checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
109+
110+ [[package]]
111 name = "constant_time_eq"
112 version = "0.3.1"
113 source = "registry+https://github.com/rust-lang/crates.io-index"
114 @@ -338,7 +433,7 @@ version = "0.6.0"
115 source = "registry+https://github.com/rust-lang/crates.io-index"
116 checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a"
117 dependencies = [
118- "heck",
119+ "heck 0.4.1",
120 "proc-macro2",
121 "quote",
122 "syn",
123 @@ -512,6 +607,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
124 checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
125
126 [[package]]
127+ name = "heck"
128+ version = "0.5.0"
129+ source = "registry+https://github.com/rust-lang/crates.io-index"
130+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
131+
132+ [[package]]
133 name = "hermit-abi"
134 version = "0.3.9"
135 source = "registry+https://github.com/rust-lang/crates.io-index"
136 @@ -646,6 +747,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
137 checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
138
139 [[package]]
140+ name = "is_terminal_polyfill"
141+ version = "1.70.1"
142+ source = "registry+https://github.com/rust-lang/crates.io-index"
143+ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
144+
145+ [[package]]
146 name = "itoa"
147 version = "1.0.11"
148 source = "registry+https://github.com/rust-lang/crates.io-index"
149 @@ -797,6 +904,7 @@ dependencies = [
150 name = "maitred-debug"
151 version = "0.1.0"
152 dependencies = [
153+ "clap",
154 "futures",
155 "maitred",
156 "tokio",
157 @@ -1273,6 +1381,12 @@ dependencies = [
158 ]
159
160 [[package]]
161+ name = "strsim"
162+ version = "0.11.1"
163+ source = "registry+https://github.com/rust-lang/crates.io-index"
164+ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
165+
166+ [[package]]
167 name = "subtle"
168 version = "2.6.1"
169 source = "registry+https://github.com/rust-lang/crates.io-index"
170 @@ -1536,6 +1650,12 @@ dependencies = [
171 ]
172
173 [[package]]
174+ name = "utf8parse"
175+ version = "0.2.2"
176+ source = "registry+https://github.com/rust-lang/crates.io-index"
177+ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
178+
179+ [[package]]
180 name = "valuable"
181 version = "0.1.0"
182 source = "registry+https://github.com/rust-lang/crates.io-index"
183 diff --git a/cmd/maitred-debug/Cargo.toml b/cmd/maitred-debug/Cargo.toml
184index df1d532..9792cd0 100644
185--- a/cmd/maitred-debug/Cargo.toml
186+++ b/cmd/maitred-debug/Cargo.toml
187 @@ -4,6 +4,7 @@ version = "0.1.0"
188 edition = "2021"
189
190 [dependencies]
191+ clap = { version = "4.5.16", features = ["derive"] }
192 futures = "0.3.30"
193
194 maitred = {path = "../../maitred"}
195 diff --git a/cmd/maitred-debug/src/main.rs b/cmd/maitred-debug/src/main.rs
196index 03008f2..2057c8d 100644
197--- a/cmd/maitred-debug/src/main.rs
198+++ b/cmd/maitred-debug/src/main.rs
199 @@ -1,3 +1,4 @@
200+ use clap::Parser;
201 use tracing::Level;
202
203 use maitred::{
204 @@ -13,8 +14,20 @@ async fn print_message(message: Message<'static>) -> Result<(), DeliveryError> {
205 Ok(())
206 }
207
208+ #[derive(Parser, Debug)]
209+ #[clap(author, version, about, long_about = None)]
210+ struct Args {
211+ /// Enable DKIM Verification of Incoming E-Mail.
212+ #[clap(long, default_value_t = true)]
213+ dkim: bool,
214+ /// Enagle SPF Verification of Incoming E-Mail.
215+ #[clap(long, default_value_t = true)]
216+ spf: bool,
217+ }
218+
219 #[tokio::main]
220 async fn main() -> Result<(), Error> {
221+ let args = Args::parse();
222 // Create a subscriber that logs events to the console
223 tracing_subscriber::fmt()
224 .compact()
225 @@ -32,6 +45,8 @@ async fn main() -> Result<(), Error> {
226 let message = message.clone();
227 async move { print_message(message.to_owned()).await }
228 }))
229+ .dkim_verification(args.dkim)
230+ .spf_verification(args.spf)
231 .with_session_opts(SessionOptions::default().plain_auth(PlainAuthFunc(
232 |authcid: &str, authzid: &str, passwd: &str| {
233 println!(
234 diff --git a/maitred/src/lib.rs b/maitred/src/lib.rs
235index 9188bd4..736e17d 100644
236--- a/maitred/src/lib.rs
237+++ b/maitred/src/lib.rs
238 @@ -35,11 +35,11 @@
239 //! .address("127.0.0.1:2525")
240 //! .with_milter(MilterFunc(|message: &Message<'static>| {
241 //! let message = message.clone();
242- //! Box::pin(async move { Ok(message.to_owned()) })
243+ //! async move { Ok(message.to_owned()) }
244 //! }))
245 //! .with_delivery(DeliveryFunc(|message: &Message<'static>| {
246 //! let message = message.clone();
247- //! Box::pin(async move { print_message(message.to_owned()).await })
248+ //! async move { print_message(message.to_owned()).await }
249 //! }))
250 //! .with_session_opts(SessionOptions::default());
251 //! // mail_server.listen().await?;
252 diff --git a/maitred/src/server.rs b/maitred/src/server.rs
253index 77ce634..4d668d1 100644
254--- a/maitred/src/server.rs
255+++ b/maitred/src/server.rs
256 @@ -1,4 +1,4 @@
257- use std::rc::Rc;
258+ use std::net::SocketAddr;
259 use std::sync::Arc;
260 use std::time::Duration;
261
262 @@ -7,6 +7,7 @@ use crossbeam_deque::Stealer;
263 use crossbeam_deque::Worker as WorkQueue;
264 use futures::SinkExt;
265 use futures::StreamExt;
266+ use mail_auth::Resolver;
267 use smtp_proto::Request;
268 use tokio::net::TcpListener;
269 use tokio::sync::mpsc::Sender;
270 @@ -22,7 +23,6 @@ use crate::milter::Milter;
271 use crate::session::{Session, SessionOptions};
272 use crate::smtp_response;
273 use crate::transport::{Command, Transport, TransportError};
274- use crate::validation::Validation;
275 use crate::worker::{Packet, Worker};
276 use crate::{Response, SmtpResponse};
277
278 @@ -59,12 +59,15 @@ pub(crate) enum ClientError {
279 pub struct Server {
280 address: String,
281 global_timeout: Duration,
282- options: Option<Rc<crate::session::SessionOptions>>,
283+ options: Option<crate::session::SessionOptions>,
284 milter: Option<Arc<dyn Milter>>,
285 delivery: Option<Arc<dyn Delivery>>,
286 n_threads: usize,
287 shutdown_handles: Vec<Sender<bool>>,
288 dkim_verification: bool,
289+ spf_verification: bool,
290+ current_addr: Option<SocketAddr>,
291+ resolver: Option<Arc<Mutex<Resolver>>>,
292 }
293
294 impl Default for Server {
295 @@ -78,6 +81,9 @@ impl Default for Server {
296 n_threads: std::thread::available_parallelism().unwrap().into(),
297 shutdown_handles: vec![],
298 dkim_verification: false,
299+ spf_verification: false,
300+ current_addr: None,
301+ resolver: None,
302 }
303 }
304 }
305 @@ -101,7 +107,7 @@ impl Server {
306 /// sessions. Most custom behavior is implemented here but not specifying
307 /// any options will provide a limited but functional server.
308 pub fn with_session_opts(mut self, opts: SessionOptions) -> Self {
309- self.options = Some(Rc::new(opts));
310+ self.options = Some(opts);
311 self
312 }
313
314 @@ -123,11 +129,18 @@ impl Server {
315 self
316 }
317
318+ /// Perform DKIM Verification
319 pub fn dkim_verification(mut self, enabled: bool) -> Self {
320 self.dkim_verification = enabled;
321 self
322 }
323
324+ /// Perform SPF Verification
325+ pub fn spf_verification(mut self, enabled: bool) -> Self {
326+ self.spf_verification = enabled;
327+ self
328+ }
329+
330 async fn process<T>(
331 &self,
332 mut framed: Framed<T, Transport>,
333 @@ -136,13 +149,19 @@ impl Server {
334 where
335 T: tokio::io::AsyncRead + tokio::io::AsyncWrite + std::marker::Unpin,
336 {
337- let mut session = Session::default();
338- if let Some(opts) = &self.options {
339- session = session.with_options(opts.clone());
340+ let mut session_opts = self.options.clone().unwrap_or_default();
341+
342+ if let Some(addr) = self.current_addr {
343+ session_opts = session_opts.ip_addr(addr.ip());
344 }
345
346+ let mut session = Session::default()
347+ .with_options(session_opts)
348+ .resolver(self.resolver.clone())
349+ .spf_verification(self.spf_verification);
350+
351 let greeting = session.greeting();
352- let mut session = session;
353+
354 // send inital server greeting
355 framed.send(greeting).await?;
356
357 @@ -243,7 +262,7 @@ impl Server {
358 Ok(())
359 }
360
361- async fn spawn_workers(&mut self, global_queue: Arc<Injector<Packet>>, validation: &Validation) {
362+ async fn spawn_workers(&mut self, global_queue: Arc<Injector<Packet>>) {
363 let local_queues: Vec<WorkQueue<Packet>> = (0..self.n_threads)
364 .map(|_| WorkQueue::<Packet>::new_fifo())
365 .collect();
366 @@ -260,7 +279,8 @@ impl Server {
367 let stealers = stealers.clone();
368 let milter = self.milter.clone();
369 let delivery = self.delivery.clone();
370- let thread_validation = validation.clone();
371+ let resolver = self.resolver.clone();
372+ let dkim_verification = self.dkim_verification;
373 tokio::task::spawn(async move {
374 let mut worker = Worker {
375 milter,
376 @@ -269,7 +289,8 @@ impl Server {
377 stealers,
378 local_queue: Arc::new(Mutex::new(local_queue)),
379 shutdown_rx,
380- validation: thread_validation,
381+ resolver,
382+ dkim_verification,
383 };
384 worker.process().await
385 })
386 @@ -294,14 +315,18 @@ impl Server {
387 pub async fn listen(&mut self) -> Result<(), Error> {
388 let listener = TcpListener::bind(&self.address).await?;
389 tracing::info!("Mail server listening @ {}", self.address);
390+ self.resolver = if self.spf_verification || self.dkim_verification {
391+ Some(Arc::new(Mutex::new(Resolver::new_system_conf().unwrap())))
392+ } else {
393+ None
394+ };
395 let global_queue = Arc::new(Injector::<Packet>::new());
396- let mut validation = Validation::new().await;
397- validation.dkim_enabled = self.dkim_verification;
398- self.spawn_workers(global_queue.clone(), &validation).await;
399+ self.spawn_workers(global_queue.clone()).await;
400 loop {
401- let (socket, _) = listener.accept().await.unwrap();
402- let addr = socket.local_addr()?;
403- tracing::info!("Accepted connection on: {:?}", addr);
404+ let (socket, addr) = listener.accept().await.unwrap();
405+ self.current_addr = Some(addr);
406+ let local_addr = socket.local_addr()?;
407+ tracing::info!("Accepted connection on: {:?}", local_addr);
408 let pipelining = self
409 .options
410 .as_ref()
411 diff --git a/maitred/src/session.rs b/maitred/src/session.rs
412index f1ae928..242b7bf 100644
413--- a/maitred/src/session.rs
414+++ b/maitred/src/session.rs
415 @@ -1,3 +1,4 @@
416+ use std::net::IpAddr;
417 use std::rc::Rc;
418 use std::result::Result as StdResult;
419 use std::str::FromStr;
420 @@ -6,8 +7,10 @@ use std::sync::Arc;
421 use bytes::Bytes;
422 use email_address::EmailAddress;
423
424+ use mail_auth::Resolver;
425 use mail_parser::{Message, MessageParser};
426 use smtp_proto::{EhloResponse, Request, Response as SmtpResponse};
427+ use tokio::sync::Mutex;
428 use url::Host;
429
430 use crate::auth::{AuthData, PlainAuth};
431 @@ -90,7 +93,7 @@ pub struct SessionOptions {
432 pub list_expansion: Option<Arc<dyn Expansion>>,
433 pub verification: Option<Arc<dyn Verify>>,
434 pub plain_auth: Option<Arc<dyn PlainAuth>>,
435- pub validation: Option<Validation>,
436+ pub ip_addr: Option<IpAddr>,
437 }
438
439 impl Default for SessionOptions {
440 @@ -104,7 +107,7 @@ impl Default for SessionOptions {
441 list_expansion: None,
442 verification: None,
443 plain_auth: None,
444- validation: None,
445+ ip_addr: None,
446 }
447 }
448 }
449 @@ -154,6 +157,11 @@ impl SessionOptions {
450 self.plain_auth = Some(Arc::new(plain_auth));
451 self
452 }
453+
454+ pub fn ip_addr(mut self, ip_addr: IpAddr) -> Self {
455+ self.ip_addr = Some(ip_addr);
456+ self
457+ }
458 }
459
460 /// Stateful connection that coresponds to a single SMTP session.
461 @@ -169,15 +177,30 @@ pub(crate) struct Session {
462 // If an active data transfer is taking place
463 data_transfer: Option<DataTransfer>,
464 initialized: Option<Mode>,
465+ spf_verification: bool,
466 auth_initialized: bool,
467 // session options
468 opts: Rc<SessionOptions>,
469+ // previously ran commands
470+ // TODO pipeline still partially broken
471+ history: Vec<Request<String>>,
472+ resolver: Option<Arc<Mutex<Resolver>>>,
473 }
474
475 impl Session {
476+ pub fn spf_verification(mut self, verify_spf: bool) -> Self {
477+ self.spf_verification = verify_spf;
478+ self
479+ }
480+
481+ pub fn resolver(mut self, resolver: Option<Arc<Mutex<Resolver>>>) -> Self {
482+ self.resolver = resolver;
483+ self
484+ }
485+
486 /// Configure a session with various options that effect it's behavior.
487- pub fn with_options(mut self, opts: Rc<SessionOptions>) -> Self {
488- self.opts = opts;
489+ pub fn with_options(mut self, opts: SessionOptions) -> Self {
490+ self.opts = Rc::new(opts);
491 self
492 }
493
494 @@ -190,6 +213,7 @@ impl Session {
495 // FIXME: is the hostname reset?
496 // self.hostname = None;
497 self.data_transfer = None;
498+ self.history = Vec::new();
499 }
500 /// A greeting must be sent at the start of an SMTP connection when it is
501 /// first initialized.
502 @@ -302,6 +326,7 @@ impl Session {
503 /// parsed bytes from the transfer.
504 /// FIXME: Not at all reasonable yet
505 pub async fn process(&mut self, req: &Request<String>) -> Result {
506+ self.history.push(req.clone());
507 match req {
508 Request::Ehlo { host } => {
509 self.hostname = Some(
510 @@ -359,6 +384,30 @@ impl Session {
511 )
512 })?;
513 self.mail_from = Some(mail_from.clone());
514+ if self.spf_verification {
515+ tracing::info!("Running SPF Validation");
516+ let ip_addr = self.opts.ip_addr.ok_or(smtp_response!(
517+ 500,
518+ 0,
519+ 0,
520+ 0,
521+ "Client has no IP Address"
522+ ))?;
523+ let helo_domain = self
524+ .hostname
525+ .clone()
526+ .ok_or(smtp_response!(500, 0, 0, 0, "hostname is not specified"))?
527+ .to_string();
528+ let our_domain = self.opts.our_hostname.clone();
529+ let resolver = self.resolver.as_ref().expect("Resolver not configured");
530+ let resolver = resolver.lock().await;
531+ let pass = Validation(resolver)
532+ .verify_spf(ip_addr, &helo_domain, &our_domain, mail_from.as_str())
533+ .await;
534+ if !pass {
535+ return Err(smtp_response!(500, 0, 0, 0, "SPF Verification Failed"));
536+ }
537+ }
538 Ok(vec![smtp_response!(250, 0, 0, 0, "OK")])
539 }
540 Request::Rcpt { to } => {
541 diff --git a/maitred/src/validation.rs b/maitred/src/validation.rs
542index b9d2ed2..90525ed 100644
543--- a/maitred/src/validation.rs
544+++ b/maitred/src/validation.rs
545 @@ -1,36 +1,18 @@
546 use std::net::IpAddr;
547
548 use mail_auth::{AuthenticatedMessage, DkimResult, Resolver};
549+ use tokio::sync::MutexGuard;
550
551 /// Wraps around mail_auth to do various email authentication / validation
552- #[derive(Clone)]
553- pub struct Validation {
554- resolver: Resolver,
555- pub dkim_enabled: bool,
556- pub spf_enabled: bool,
557- }
558-
559- impl Validation {
560- pub async fn new() -> Self {
561- let resolver = Resolver::new_system_conf().unwrap();
562- Validation {
563- resolver,
564- dkim_enabled: false,
565- spf_enabled: false,
566- }
567- }
568+ pub(crate) struct Validation<'a>(pub MutexGuard<'a, Resolver>);
569
570+ impl Validation<'_> {
571 pub async fn verify_dkim(&self, message: &[u8]) -> bool {
572 let authenticated = AuthenticatedMessage::parse(message).unwrap();
573- let passed = self
574- .resolver
575- .verify_dkim(&authenticated)
576- .await
577- .iter()
578- .all(|s| {
579- tracing::info!("{:?}", s);
580- matches!(s.result(), DkimResult::Pass)
581- });
582+ let passed = self.0.verify_dkim(&authenticated).await.iter().all(|s| {
583+ tracing::info!("{:?}", s);
584+ matches!(s.result(), DkimResult::Pass)
585+ });
586 passed
587 }
588
589 @@ -42,8 +24,8 @@ impl Validation {
590 sender: &str,
591 ) -> bool {
592 let output = self
593- .resolver
594- .verify_spf_sender(ip, helo_domain, host_domain, sender)
595+ .0
596+ .verify_spf(ip, helo_domain, host_domain, sender)
597 .await;
598 matches!(output.result(), mail_auth::SpfResult::Pass)
599 }
600 diff --git a/maitred/src/worker.rs b/maitred/src/worker.rs
601index 2e55725..652c881 100644
602--- a/maitred/src/worker.rs
603+++ b/maitred/src/worker.rs
604 @@ -3,7 +3,7 @@ use std::{iter, time::Duration};
605
606 use crossbeam_deque::{Injector, Stealer, Worker as WorkQueue};
607 use email_address::EmailAddress;
608- use mail_auth::{AuthenticatedMessage, DkimResult};
609+ use mail_auth::Resolver;
610 use mail_parser::Message;
611 use tokio::sync::{mpsc::Receiver, Mutex};
612 use url::Host;
613 @@ -51,7 +51,8 @@ pub(crate) struct Worker {
614 pub stealers: Vec<Stealer<Packet>>,
615 pub local_queue: Arc<Mutex<WorkQueue<Packet>>>,
616 pub shutdown_rx: Receiver<bool>,
617- pub validation: Validation,
618+ pub resolver: Option<Arc<Mutex<Resolver>>>,
619+ pub dkim_verification: bool,
620 }
621
622 impl Worker {
623 @@ -87,9 +88,14 @@ impl Worker {
624 .map(|id| id.to_string())
625 .unwrap_or(String::from("Unknown Message"));
626 let mut dkim_passed: Option<bool> = None;
627- if self.validation.dkim_enabled {
628+ if self.dkim_verification {
629 tracing::info!("DKIM Verification for {}", message_id);
630- let passed = self.validation.verify_dkim(&message_bytes).await;
631+ let resolver = self.resolver.as_ref().expect("Resolver not configured");
632+ let resolver = resolver.lock().await;
633+ let passed =
634+ Validation(resolver)
635+ .verify_dkim(message_bytes)
636+ .await;
637 dkim_passed = Some(passed);
638 }
639 if let Some(milter) = &self.milter {