Commit
+253 -55 +/-8 browse
1 | diff --git a/Cargo.lock b/Cargo.lock |
2 | index 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 |
184 | index 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 |
196 | index 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 |
235 | index 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 |
253 | index 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 |
412 | index 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 |
542 | index 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 |
601 | index 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 { |