Author:
Hash:
Timestamp:
+253 -55 +/-8 browse
Kevin Schoon [me@kevinschoon.com]
331d7e8c1694b1ddddb3a99cd1c69c5e1bd20fcb
Wed, 04 Sep 2024 09:00:23 +0000 (1.2 years ago)
| 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 { |